/* -*- c++ -*- */
/*
 * Copyright 2016 Free Software Foundation, Inc.
 *
 * This file is part of GNU Radio
 *
 * SPDX-License-Identifier: GPL-3.0-or-later
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <gnuradio/attributes.h>
#include <gnuradio/digital/header_buffer.h>
#include <volk/volk_alloc.hh>
#include <boost/test/unit_test.hpp>
#include <cstdio>

BOOST_AUTO_TEST_CASE(test_add8)
{
    size_t len = sizeof(uint8_t);
    volk::vector<uint8_t> buf(len);

    gr::digital::header_buffer header(buf.data());
    header.add_field8(0xAF);

    BOOST_REQUIRE_EQUAL(len, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[0]);

    header.clear();
    BOOST_REQUIRE_EQUAL((size_t)0, header.length());
}

BOOST_AUTO_TEST_CASE(test_add16)
{
    size_t len = sizeof(uint16_t);
    volk::vector<uint8_t> buf(len);

    uint16_t data = 0xAF5C;

    gr::digital::header_buffer header(buf.data());
    header.add_field16(data);

    // Test standard add of a uint16
    BOOST_REQUIRE_EQUAL(len, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[1]);

    // Clear; test to make sure it's clear
    header.clear();
    BOOST_REQUIRE_EQUAL((size_t)0, header.length());

    // Test adding some subset of bits (must be a byte boundary)
    header.add_field16(data, 8);
    BOOST_REQUIRE_EQUAL((size_t)1, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[0]);
    header.clear();

    // Test adding and byte swapping
    header.add_field16(data, 16, true);
    BOOST_REQUIRE_EQUAL((size_t)2, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[1]);
    header.clear();

    // Test adding some subset of bits and byte swapping
    header.add_field16(data, 8, true);
    BOOST_REQUIRE_EQUAL((size_t)1, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[0]);
    header.clear();
}

BOOST_AUTO_TEST_CASE(test_add32)
{
    size_t len = sizeof(uint32_t);
    volk::vector<uint8_t> buf(len);

    uint32_t data = 0xAF5C7654;

    gr::digital::header_buffer header(buf.data());
    header.add_field32(data);

    // Test standard add of a uint32
    BOOST_REQUIRE_EQUAL(len, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[3]);

    // Clear; test to make sure it's clear
    header.clear();
    BOOST_REQUIRE_EQUAL((size_t)0, header.length());

    // Test adding some subset of bits (must be a byte boundary)
    header.add_field32(data, 8);
    BOOST_REQUIRE_EQUAL((size_t)1, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[0]);
    header.clear();

    // Test adding and byte swapping
    header.add_field32(data, 32, true);
    BOOST_REQUIRE_EQUAL((size_t)4, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[3]);
    header.clear();

    // Test adding some subset of bits and byte swapping
    header.add_field32(data, 24, true);
    BOOST_REQUIRE_EQUAL((size_t)3, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[2]);
    header.clear();
}

BOOST_AUTO_TEST_CASE(test_add64)
{
    size_t len = sizeof(uint64_t);
    volk::vector<uint8_t> buf(len);

    uint64_t data = 0xAF5C765432104567;

    gr::digital::header_buffer header(buf.data());
    header.add_field64(data);

    // Test standard add of a uint64
    BOOST_REQUIRE_EQUAL(len, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[3]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x32, header.header()[4]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x10, header.header()[5]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[6]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x67, header.header()[7]);

    // Clear; test to make sure it's clear
    header.clear();
    BOOST_REQUIRE_EQUAL((size_t)0, header.length());

    // Test adding some subset of bits (must be a byte boundary)
    header.add_field64(data, 48);
    BOOST_REQUIRE_EQUAL((size_t)6, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x32, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x10, header.header()[3]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[4]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x67, header.header()[5]);
    header.clear();

    // Test adding and byte swapping
    header.add_field64(data, 64, true);
    BOOST_REQUIRE_EQUAL((size_t)8, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x67, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x10, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x32, header.header()[3]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[4]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[5]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x5C, header.header()[6]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xAF, header.header()[7]);
    header.clear();

    // Test adding some subset of bits and byte swapping
    header.add_field64(data, 40, true);
    BOOST_REQUIRE_EQUAL((size_t)5, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x67, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x10, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x32, header.header()[3]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[4]);
    header.clear();
}

BOOST_AUTO_TEST_CASE(test_add_many)
{
    size_t len = (32 + 64 + 8 + 16 + 32) / 8;
    volk::vector<uint8_t> buf(len);

    gr::digital::header_buffer header(buf.data());
    header.add_field32(0x01234567);
    header.add_field64(0x89ABCDEFFEDCBA98);
    header.add_field8(0x76);
    header.add_field16(0x5432);
    header.add_field32(0x10012345);

    BOOST_REQUIRE_EQUAL(len, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x01, header.header()[0]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x23, header.header()[1]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[2]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x67, header.header()[3]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x89, header.header()[4]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xAB, header.header()[5]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xCD, header.header()[6]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xEF, header.header()[7]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xFE, header.header()[8]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xDC, header.header()[9]);
    BOOST_REQUIRE_EQUAL((uint8_t)0xBA, header.header()[10]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x98, header.header()[11]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x76, header.header()[12]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x54, header.header()[13]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x32, header.header()[14]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x10, header.header()[15]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x01, header.header()[16]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x23, header.header()[17]);
    BOOST_REQUIRE_EQUAL((uint8_t)0x45, header.header()[18]);
}

BOOST_AUTO_TEST_CASE(test_extract8)
{
    gr::digital::header_buffer header;

    uint64_t data = 0x0123456701234567;

    // Packed format: 0x80C4A2E680C4A2E6

    volk_64u_byteswap(&data, 1);
    for (int i = 0; i < 64; i++) {
        header.insert_bit((data >> i) & 0x01);
    }

    uint8_t x0 = header.extract_field8(0);
    uint8_t x1 = header.extract_field8(12, 8);
    uint8_t x2 = header.extract_field8(12, 4);
    uint8_t x3 = header.extract_field8(8, 8, false, true);

    BOOST_REQUIRE_EQUAL((size_t)64, header.length());
    BOOST_REQUIRE_EQUAL((uint8_t)0x80, x0);
    BOOST_REQUIRE_EQUAL((uint8_t)0x4A, x1);
    BOOST_REQUIRE_EQUAL((uint8_t)0x04, x2);
    BOOST_REQUIRE_EQUAL((uint8_t)0x23, x3);
}

BOOST_AUTO_TEST_CASE(test_extract16)
{
    gr::digital::header_buffer header;

    uint64_t data = 0x0123456701234567;

    // Packed format: 0x80C4A2E680C4A2E6

    volk_64u_byteswap(&data, 1);
    for (int i = 0; i < 64; i++) {
        header.insert_bit((data >> i) & 0x01);
    }

    uint16_t x0 = header.extract_field16(0);
    uint16_t x1 = header.extract_field16(12, 16);
    uint16_t x2 = header.extract_field16(12, 12);
    uint16_t x3 = header.extract_field16(16, 16, false, true);

    BOOST_REQUIRE_EQUAL((size_t)64, header.length());
    BOOST_REQUIRE_EQUAL((uint16_t)0x80C4, x0);
    BOOST_REQUIRE_EQUAL((uint16_t)0x4A2E, x1);
    BOOST_REQUIRE_EQUAL((uint16_t)0x04A2, x2);
    BOOST_REQUIRE_EQUAL((uint16_t)0x6745, x3);
}

BOOST_AUTO_TEST_CASE(test_extract32)
{
    gr::digital::header_buffer header;

    uint64_t data = 0x0123456701234567;

    // Packed format: 0x80C4A2E680C4A2E6

    volk_64u_byteswap(&data, 1);
    for (int i = 0; i < 64; i++) {
        header.insert_bit((data >> i) & 0x01);
    }

    uint32_t x0 = header.extract_field32(0);
    uint32_t x1 = header.extract_field32(12, 32);
    uint32_t x2 = header.extract_field32(12, 24);
    uint32_t x3 = header.extract_field32(16, 16, false, true);

    BOOST_REQUIRE_EQUAL((size_t)64, header.length());
    BOOST_REQUIRE_EQUAL((uint32_t)0x80C4A2E6, x0);
    BOOST_REQUIRE_EQUAL((uint32_t)0x4A2E680C, x1);
    BOOST_REQUIRE_EQUAL((uint32_t)0x004A2E68, x2);
    BOOST_REQUIRE_EQUAL((uint32_t)0x00006745, x3);
}

BOOST_AUTO_TEST_CASE(test_extract64)
{
    gr::digital::header_buffer header;

    uint64_t data = 0x0123456701234567;

    // Packed format: 0x80C4A2E680C4A2E6

    volk_64u_byteswap(&data, 1);
    for (int i = 0; i < 64; i++) {
        header.insert_bit((data >> i) & 0x01);
    }

    uint64_t x0 = header.extract_field64(0);
    uint64_t x1 = header.extract_field64(0, 32);
    uint64_t x2 = header.extract_field64(0, 44);
    uint64_t x3 = header.extract_field32(16, 16, false, true);

    BOOST_REQUIRE_EQUAL((size_t)64, header.length());
    BOOST_REQUIRE_EQUAL((uint64_t)0x80C4A2E680C4A2E6, x0);
    BOOST_REQUIRE_EQUAL((uint64_t)0x0000000080C4A2E6, x1);
    BOOST_REQUIRE_EQUAL((uint64_t)0x0000080C4A2E680C, x2);
    BOOST_REQUIRE_EQUAL((uint64_t)0x0000000000006745, x3);
}

bool check_error_msg(const std::runtime_error& ex)
{
    BOOST_CHECK_EQUAL(
        ex.what(), std::string("header_buffer::extract_field for length must be <= 16"));
    return true;
}

BOOST_AUTO_TEST_CASE(test_extract_many)
{
    gr::digital::header_buffer header;

    uint64_t data = 0x0123456701234567;

    // Packed format: 0x80C4A2E680C4A2E6

    volk_64u_byteswap(&data, 1);
    for (int i = 0; i < 64; i++) {
        header.insert_bit((data >> i) & 0x01);
    }

    uint64_t x0 = header.extract_field64(0);
    uint16_t x1 = header.extract_field16(28, 12);
    uint32_t x2 = header.extract_field32(40, 21);
    uint16_t x3 = header.extract_field16(1, 12);
    uint8_t x4 = header.extract_field8(7, 5);
    uint8_t x5 = header.extract_field8(7, 5, false, true);
    uint8_t x6 = header.extract_field<uint8_t>(7, 5, false, true);
    BOOST_CHECK_EXCEPTION(header.extract_field<uint16_t>(7, 20, false, true),
                          std::runtime_error,
                          check_error_msg);

    BOOST_REQUIRE_EQUAL((size_t)64, header.length());
    BOOST_REQUIRE_EQUAL((uint64_t)0x80C4A2E680C4A2E6, x0);
    BOOST_REQUIRE_EQUAL((uint16_t)0x0680, x1);
    BOOST_REQUIRE_EQUAL((uint32_t)0x0018945C, x2);
    BOOST_REQUIRE_EQUAL((uint16_t)0x0018, x3);
    BOOST_REQUIRE_EQUAL((uint8_t)0x0C, x4);
    BOOST_REQUIRE_EQUAL((uint8_t)0x06, x5);
    BOOST_REQUIRE_EQUAL((uint8_t)0x06, x6);
}
