/*
  Copyright 2020-2022 The cc65 Authors

  This software is provided 'as-is', without any express or implied
  warranty. In no event will the authors be held liable for any damages
  arising from the use of this software.

  Permission is granted to anyone to use this software for any purpose,
  including commercial applications, and to alter it and redistribute it
  freely, subject to the following restrictions:

  1. The origin of this software must not be misrepresented; you must not
     claim that you wrote the original software. If you use this software
     in a product, an acknowledgment in the product documentation would be
     appreciated but is not required.
  2. Altered source versions must be plainly marked as such, and must not be
     misrepresented as being the original software.
  3. This notice may not be removed or altered from any source distribution.
*/

/*
  Tests of long bit-field packing and typedef works with them; see issues below
  - packing: https://github.com/cc65/cc65/issues/1054
  - typedef: https://github.com/cc65/cc65/pull/1662
  - long bit-field support: https://github.com/cc65/cc65/issues/1131
*/

#include <stdio.h>

static unsigned char failures = 0;

typedef unsigned long field_type;

static struct four_bits {
    field_type x : 4;
} fb = {1};

static void test_four_bits(void)
{
    if (sizeof(struct four_bits) != 1) {
        printf("Got sizeof(struct four_bits) = %zu, expected 1.\n",
               sizeof(struct four_bits));
        failures++;
    }

    if (fb.x != 1) {
        printf("Got fb.x = %u, expected 1.\n", fb.x);
        failures++;
    }

    fb.x = 3;

    if (fb.x != 3) {
        printf("Got fb.x = %u, expected 3.\n", fb.x);
        failures++;
    }
}

/*
  Logic is somewhat diferent for bit-fields that end a struct vs
  having additional fields.
*/

static struct four_bits_with_long {
    field_type x : 4;
    field_type y;
} fbi = {1, 2};

static void test_four_bits_with_long(void)
{
    /* The first 4-bit bit-field just takes one byte, so the size is 5.  */
    if (sizeof(struct four_bits_with_long) != 5) {
        printf("Got sizeof(struct four_bits_with_long) = %zu, expected 5.\n",
               sizeof(struct four_bits_with_long));
        failures++;
    }

    if (fbi.x != 1) {
        printf("Got fbi.x = %u, expected 1.\n", fbi.x);
        failures++;
    }

    if (fbi.y != 2) {
        printf("Got fbi.y = %lu, expected 2.\n", fbi.y);
        failures++;
    }

    fbi.x = 3;
    fbi.y = 65537;

    if (fbi.x != 3) {
        printf("Got fbi.x = %u, expected 3.\n", fbi.x);
        failures++;
    }

    if (fbi.y != 65537) {
        printf("Got fbi.y = %lu, expected 65537.\n", fbi.y);
        failures++;
    }
}

static struct overlap {
    field_type x : 10;
    field_type y : 10;
} o = {11, 22};

/* Tests that bit-fields can share allocation units. */
static void test_overlap(void)
{
    if (sizeof(struct overlap) != 3) {
        printf("Got sizeof(struct overlap) = %zu, expected 3.\n",
               sizeof(struct overlap));
        failures++;
    }

    if (o.x != 11) {
        printf("Got o.x = %u, expected 11.\n", o.x);
        failures++;
    }

    if (o.y != 22) {
        printf("Got o.y = %u, expected 22.\n", o.y);
        failures++;
    }

    o.x = 33;
    o.y = 44;

    if (o.x != 33) {
        printf("Got o.x = %u, expected 33.\n", o.x);
        failures++;
    }

    if (o.y != 44) {
        printf("Got o.y = %u, expected 44.\n", o.y);
        failures++;
    }
}

static struct overlap_with_long {
    field_type x : 10;
    field_type y : 10;
    field_type z;
} oi = {111, 222, 333};

static void test_overlap_with_long(void)
{
    /* First two fields in 3 bytes, then another 4 bytes. */
    if (sizeof(struct overlap_with_long) != 7) {
        printf("Got sizeof(struct overlap_with_long) = %zu, expected 7.\n",
               sizeof(struct overlap_with_long));
        failures++;
    }

    if (oi.x != 111) {
        printf("Got oi.x = %u, expected 111.\n", oi.x);
        failures++;
    }

    if (oi.y != 222) {
        printf("Got oi.y = %u, expected 222.\n", oi.y);
        failures++;
    }

    if (oi.z != 333) {
        printf("Got oi.z = %u, expected 333.\n", oi.z);
        failures++;
    }

    oi.x = 444;
    oi.y = 555;
    oi.z = 4294967295;

    if (oi.x != 444) {
        printf("Got oi.x = %u, expected 444.\n", oi.x);
        failures++;
    }

    if (oi.y != 555) {
        printf("Got oi.y = %u, expected 555.\n", oi.y);
        failures++;
    }

    if (oi.z != 4294967295) {
        printf("Got oi.z = %lu, expected 4294967295.\n", oi.z);
        failures++;
    }
}

static struct full_width {
    field_type x : 8;
    field_type y : 16;
    field_type z : 32;
} fw = {255, 17, 1};

static void test_full_width(void)
{
    if (sizeof(struct full_width) != 7) {
        printf("Got sizeof(struct full_width) = %zu, expected 7.\n",
               sizeof(struct full_width));
        failures++;
    }

    if (fw.x != 255) {
        printf("Got fw.x = %u, expected 255.\n", fw.x);
        failures++;
    }

    if (fw.y != 17) {
        printf("Got fw.y = %u, expected 17.\n", fw.y);
        failures++;
    }

    if (fw.z != 1) {
        printf("Got fw.z = %lu, expected 1.\n", fw.z);
        failures++;
    }

    fw.x = 42;
    fw.y = 1023;
    fw.z = 65537;

    if (fw.x != 42) {
        printf("Got fw.x = %u, expected 42.\n", fw.x);
        failures++;
    }

    if (fw.y != 1023) {
        printf("Got fw.y = %u, expected 1023.\n", fw.y);
        failures++;
    }

    if (fw.z != 65537) {
        printf("Got fw.z = %lu, expected 65537.\n", fw.z);
        failures++;
    }
}

static struct aligned_end {
    field_type : 2;
    field_type x : 6;
    field_type : 3;
    field_type y : 13;
    field_type : 14;
    field_type z : 18;
    /* w crosses a byte boundary, but fits in a byte when shifted. */
    field_type : 6;
    field_type w : 7;
} ae = {63, 17, 1, 100};

static void test_aligned_end(void)
{
    if (sizeof(struct aligned_end) != 9) {
        printf("Got sizeof(struct aligned_end) = %zu, expected 9.\n",
               sizeof(struct aligned_end));
        failures++;
    }

    if (ae.x != 63) {
        printf("Got ae.x = %u, expected 63.\n", ae.x);
        failures++;
    }

    if (ae.y != 17) {
        printf("Got ae.y = %u, expected 17.\n", ae.y);
        failures++;
    }

    if (ae.z != 1) {
        printf("Got ae.z = %lu, expected 1.\n", ae.z);
        failures++;
    }

    if (ae.w != 100) {
        printf("Got ae.w = %u, expected 100.\n", ae.w);
        failures++;
    }

    ae.x = 42;
    ae.y = 1023;
    ae.z = 262143;
    ae.w = 66;

    if (ae.x != 42) {
        printf("Got ae.x = %u, expected 42.\n", ae.x);
        failures++;
    }

    if (ae.y != 1023) {
        printf("Got ae.y = %u, expected 1023.\n", ae.y);
        failures++;
    }

    if (ae.z != 262143) {
        printf("Got ae.z = %lu, expected 262143.\n", ae.z);
        failures++;
    }

    if (ae.w != 66) {
        printf("Got ae.w = %u, expected 66.\n", ae.w);
        failures++;
    }
}

int main(void)
{
    test_four_bits();
    test_four_bits_with_long();
    test_overlap();
    test_overlap_with_long();
    test_full_width();
    test_aligned_end();
    printf("failures: %u\n", failures);
    return failures;
}
