/*
 *  Copyright (c) 2021, The OpenThread Authors.
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *  1. Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *  2. Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *  3. Neither the name of the copyright holder nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 *  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 *  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 *  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 *  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 *  POSSIBILITY OF SUCH DAMAGE.
 */

#include "test_platform.h"

#include <string.h>

#include <openthread/config.h>

#include "test_util.hpp"
#include "common/code_utils.hpp"
#include "common/heap_data.hpp"
#include "common/heap_string.hpp"

namespace ot {

void PrintString(const char *aName, const Heap::String &aString)
{
    if (aString.IsNull())
    {
        printf("%s = (null)\n", aName);
    }
    else
    {
        printf("%s = [%zu] \"%s\"\n", aName, strlen(aString.AsCString()), aString.AsCString());
    }
}

void VerifyString(const char *aName, const Heap::String &aString, const char *aExpectedString)
{
    PrintString(aName, aString);

    if (aExpectedString == nullptr)
    {
        VerifyOrQuit(aString.IsNull());
        VerifyOrQuit(aString.AsCString() == nullptr);
        VerifyOrQuit(aString != "something");
    }
    else
    {
        VerifyOrQuit(!aString.IsNull());
        VerifyOrQuit(aString.AsCString() != nullptr);
        VerifyOrQuit(strcmp(aString.AsCString(), aExpectedString) == 0, "String content is incorrect");
        VerifyOrQuit(aString != nullptr);
    }

    VerifyOrQuit(aString == aExpectedString);
}

// Function returning a `Heap::String` by value.
Heap::String GetName(void)
{
    Heap::String name;

    SuccessOrQuit(name.Set("name"));

    return name;
}

void TestHeapString(void)
{
    Heap::String str1;
    Heap::String str2;
    const char  *oldBuffer;

    printf("====================================================================================\n");
    printf("TestHeapString\n\n");

    printf("------------------------------------------------------------------------------------\n");
    printf("After constructor\n\n");
    VerifyString("str1", str1, nullptr);

    printf("------------------------------------------------------------------------------------\n");
    printf("Set(const char *aCstring)\n\n");
    SuccessOrQuit(str1.Set("hello"));
    VerifyString("str1", str1, "hello");
    oldBuffer = str1.AsCString();

    SuccessOrQuit(str1.Set("0123456789"));
    VerifyString("str1", str1, "0123456789");
    printf("\tDid reuse its old buffer: %s\n", str1.AsCString() == oldBuffer ? "yes" : "no");
    oldBuffer = str1.AsCString();

    SuccessOrQuit(str1.Set("9876543210"));
    VerifyString("str1", str1, "9876543210");
    printf("\tDid reuse its old buffer (same length): %s\n", str1.AsCString() == oldBuffer ? "yes" : "no");

    printf("------------------------------------------------------------------------------------\n");
    printf("Set(const Heap::String &)\n\n");
    SuccessOrQuit(str2.Set(str1));
    VerifyString("str2", str2, str1.AsCString());

    SuccessOrQuit(str1.Set(nullptr));
    VerifyString("str1", str1, nullptr);

    SuccessOrQuit(str2.Set(str1));
    VerifyString("str2", str2, nullptr);

    printf("------------------------------------------------------------------------------------\n");
    printf("Free()\n\n");
    str1.Free();
    VerifyString("str1", str1, nullptr);

    SuccessOrQuit(str1.Set("hello again"));
    VerifyString("str1", str1, "hello again");

    str1.Free();
    VerifyString("str1", str1, nullptr);

    printf("------------------------------------------------------------------------------------\n");
    printf("Set() move semantics\n\n");
    SuccessOrQuit(str1.Set("old name"));
    PrintString("str1", str1);
    SuccessOrQuit(str1.Set(GetName()), "Set() with move semantics failed");
    VerifyString("str1", str1, "name");

    printf("------------------------------------------------------------------------------------\n");
    printf("TakeFrom() and Move()\n\n");

    SuccessOrQuit(str1.Set("string to be moved"));
    str2.Free();
    VerifyString("str1", str1, "string to be moved");
    VerifyString("str2", str2, nullptr);

    str2.TakeFrom(str1.Move());
    VerifyString("str1", str1, nullptr);
    VerifyString("str2", str2, "string to be moved");

    // Test moving from self
    str2.TakeFrom(str2.Move());
    VerifyString("str2", str2, "string to be moved");

    // Test moving a null string
    str2.TakeFrom(str1.Move());
    VerifyString("str1", str1, nullptr);
    VerifyString("str2", str2, nullptr);

    printf("------------------------------------------------------------------------------------\n");
    printf("operator==() with two null string\n\n");
    str1.Free();
    str2.Free();
    VerifyString("str1", str1, nullptr);
    VerifyString("str2", str2, nullptr);
    VerifyOrQuit(str1 == str2, "operator==() failed with two null strings");

    printf("\n -- PASS\n");
}

void PrintData(const Heap::Data &aData) { DumpBuffer("data", aData.GetBytes(), aData.GetLength()); }

static const uint8_t kTestValue = 0x77;

// Function returning a `Heap::Data` by value.
Heap::Data GetData(void)
{
    Heap::Data data;

    SuccessOrQuit(data.SetFrom(&kTestValue, sizeof(kTestValue)));

    return data;
}

void VerifyData(const Heap::Data &aData, const uint8_t *aBytes, uint16_t aLength)
{
    static constexpr uint16_t kMaxLength = 100;
    uint8_t                   buffer[kMaxLength];

    PrintData(aData);

    VerifyOrQuit(aData.Matches(aBytes, aLength));
    VerifyOrQuit(!aData.Matches(aBytes, aLength + 1));

    if (aLength == 0)
    {
        VerifyOrQuit(aData.IsNull());
        VerifyOrQuit(aData.GetBytes() == nullptr);
        VerifyOrQuit(aData.GetLength() == 0);
        VerifyOrQuit(aData.Matches(nullptr, 0));
    }
    else
    {
        VerifyOrQuit(!aData.IsNull());
        VerifyOrQuit(aData.GetBytes() != nullptr);
        VerifyOrQuit(aData.GetLength() == aLength);
        VerifyOrQuit(memcmp(aData.GetBytes(), aBytes, aLength) == 0, "Data content is incorrect");

        aData.CopyBytesTo(buffer);
        VerifyOrQuit(memcmp(buffer, aBytes, aLength) == 0, "CopyBytesTo() failed");

        VerifyOrQuit(aData.Matches(buffer, aLength));
        buffer[aLength - 1]++;
        VerifyOrQuit(!aData.Matches(buffer, aLength));
    }
}

template <uint16_t kLength> void VerifyData(const Heap::Data &aData, const uint8_t (&aArray)[kLength])
{
    VerifyData(aData, &aArray[0], kLength);
}

void TestHeapData(void)
{
    Instance      *instance;
    MessagePool   *messagePool;
    Message       *message;
    Heap::Data     data;
    Heap::Data     data2;
    uint16_t       offset;
    const uint8_t *oldBuffer;

    static const uint8_t kData1[] = {10, 20, 3, 15, 100, 0, 60, 16};
    static const uint8_t kData2[] = "OpenThread HeapData";
    static const uint8_t kData3[] = {0xaa, 0xbb, 0xcc};
    static const uint8_t kData4[] = {0x11, 0x22, 0x33};

    instance = static_cast<Instance *>(testInitInstance());
    VerifyOrQuit(instance != nullptr, "Null OpenThread instance");

    messagePool = &instance->Get<MessagePool>();
    VerifyOrQuit((message = messagePool->Allocate(Message::kTypeIp6)) != nullptr);

    message->SetOffset(0);

    printf("\n\n====================================================================================\n");
    printf("TestHeapData\n\n");

    printf("------------------------------------------------------------------------------------\n");
    printf("After constructor\n");
    VerifyData(data, nullptr, 0);

    VerifyOrQuit(data.Matches(nullptr, 0));
    VerifyOrQuit(data.Matches(kData1, 0));
    VerifyOrQuit(!data.Matches(kData1, 1));

    printf("------------------------------------------------------------------------------------\n");
    printf("SetFrom(aBuffer, aLength)\n");

    SuccessOrQuit(data.SetFrom(kData1, sizeof(kData1)));
    VerifyData(data, kData1);

    SuccessOrQuit(data.SetFrom(kData2, sizeof(kData2)));
    VerifyData(data, kData2);

    SuccessOrQuit(data.SetFrom(kData3, sizeof(kData3)));
    VerifyData(data, kData3);
    oldBuffer = data.GetBytes();

    SuccessOrQuit(data.SetFrom(kData4, sizeof(kData4)));
    VerifyData(data, kData4);
    VerifyOrQuit(oldBuffer == data.GetBytes(), "did not reuse old buffer on same data length");

    SuccessOrQuit(data.SetFrom(kData4, 0));
    VerifyData(data, nullptr, 0);

    printf("------------------------------------------------------------------------------------\n");
    printf("SetFrom(aMessage)\n");

    SuccessOrQuit(message->Append(kData2));
    SuccessOrQuit(data.SetFrom(*message));
    VerifyData(data, kData2);

    SuccessOrQuit(message->Append(kData3));
    SuccessOrQuit(data.SetFrom(*message));
    PrintData(data);
    VerifyOrQuit(data.GetLength() == message->GetLength());

    message->SetOffset(sizeof(kData2));
    SuccessOrQuit(data.SetFrom(*message));
    VerifyData(data, kData3);

    SuccessOrQuit(message->Append(kData4));

    offset = 0;
    SuccessOrQuit(data.SetFrom(*message, offset, sizeof(kData2)));
    VerifyData(data, kData2);

    offset = sizeof(kData2);
    SuccessOrQuit(data.SetFrom(*message, offset, sizeof(kData3)));
    VerifyData(data, kData3);

    offset += sizeof(kData3);
    SuccessOrQuit(data.SetFrom(*message, offset, sizeof(kData4)));
    VerifyData(data, kData4);

    VerifyOrQuit(data.SetFrom(*message, offset, sizeof(kData4) + 1) == kErrorParse);
    VerifyOrQuit(data.SetFrom(*message, 0, message->GetLength() + 1) == kErrorParse);
    VerifyOrQuit(data.SetFrom(*message, 1, message->GetLength()) == kErrorParse);

    printf("------------------------------------------------------------------------------------\n");
    printf("Free()\n");

    data.Free();
    VerifyData(data, nullptr, 0);

    data.Free();
    VerifyData(data, nullptr, 0);

    printf("------------------------------------------------------------------------------------\n");
    printf("CopyBytesTo(aMessage)\n");

    SuccessOrQuit(message->SetLength(0));

    SuccessOrQuit(data.CopyBytesTo(*message));
    VerifyOrQuit(message->GetLength() == 0, "CopyBytesTo() failed");

    SuccessOrQuit(data.SetFrom(kData1, sizeof(kData1)));
    VerifyData(data, kData1);
    SuccessOrQuit(data.CopyBytesTo(*message));
    VerifyOrQuit(message->GetLength() == data.GetLength(), "CopyBytesTo() failed");
    VerifyOrQuit(message->Compare(0, kData1), "CopyBytesTo() failed");

    printf("------------------------------------------------------------------------------------\n");
    printf("SetFrom() move semantics\n\n");
    data.TakeFrom(GetData().Move());
    VerifyData(data, &kTestValue, sizeof(kTestValue));

    printf("------------------------------------------------------------------------------------\n");
    printf("TakeFrom() and Move()\n\n");

    SuccessOrQuit(data.SetFrom(kData1, sizeof(kData1)));
    VerifyData(data, kData1);
    VerifyData(data2, nullptr, 0);

    data2.TakeFrom(data.Move());
    VerifyData(data, nullptr, 0);
    VerifyData(data2, kData1);

    // Test moving from self
    data2.TakeFrom(data2.Move());
    VerifyData(data2, kData1);

    // Test moving a null data
    data2.TakeFrom(data.Move());
    VerifyData(data, nullptr, 0);
    VerifyData(data2, nullptr, 0);

    printf("\n -- PASS\n");
}

} // namespace ot

int main(void)
{
    ot::TestHeapString();
    ot::TestHeapData();
    printf("\nAll tests passed.\n");
    return 0;
}
