/*
 * Copyright (c) 2017-2025, Patrick Pelissier
 * All rights reserved.
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * + Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 * + 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS AND 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 <stdbool.h>
#include <assert.h>
#include "coverage.h"

// Override M_F for testing purpose
#define M_F(a,b) M_OVERRIDE_F(a,b)
#define M_OVERRIDE__method() , _get_method ,
#define M_OVERRIDE__method2() , _get_method2
#include "m-core.h"

#include "test-obj.h"

static bool tmp_init(void) { return true; }
static bool tmp_get_method(void) { return true; }
static bool tmp_get_method2(void) { return true; }
static bool tmp_clear(void) { return true; }

static void test_cat(void)
{
#define AandB true
#define AandBandC true
  assert (M_C(A,andB));
  assert (M_C3(A,and,B));
  assert (M_C4(A,and,B,andC));

  assert (M_C3_EMPTY(A,and,B));
  assert (M_C3_EMPTY(Aand,,B));
  assert (M_C3_EMPTY(AandB,,));

  assert (M_C5_EMPTY(A,a,n,d,B));
  assert (M_C5_EMPTY(Aand,,,,B));
  assert( M_F(tmp, _init)());
  assert( M_F(tmp, _method)());
  assert( M_F(tmp, _method2)());
  assert( M_F(tmp, _clear)());
}

static void test_power2(void)
{
  assert (M_POWEROF2_P(1));
  assert (M_POWEROF2_P(2));
  assert (!M_POWEROF2_P(3));
  assert (M_POWEROF2_P(4));
  assert (!M_POWEROF2_P(5));
  assert (!M_POWEROF2_P(6));
  assert (!M_POWEROF2_P(7));
  assert (M_POWEROF2_P(8));
}

static void test_incdec(void)
{
  assert (M_INC(0) == 1);
  assert (M_INC(1) == 2);
  assert (M_INC(2) == 3);
  assert (M_INC(3) == 4);
  assert (M_INC(4) == 5);
  assert (M_INC(5) == 6);
  assert (M_INC(6) == 7);
  assert (M_INC(7) == 8);
  assert (M_INC(8) == 9);
  assert (M_INC(9) == 10);
  assert (M_INC(10) == 11);
  assert (M_INC(11) == 12);
  assert (M_INC(12) == 13);
  assert (M_INC(13) == 14);
  assert (M_INC(14) == 15);
  assert (M_INC(15) == 16);
  assert (M_INC(16) == 17);
  assert (M_INC(17) == 18);
  assert (M_INC(18) == 19);
  assert (M_INC(19) == 20);
  assert (M_INC(20) == 21);
  assert (M_INC(21) == 22);
  assert (M_INC(22) == 23);
  assert (M_INC(23) == 24);
  assert (M_INC(24) == 25);
  assert (M_INC(25) == 26);
  assert (M_INC(26) == 27);
  assert (M_INC(27) == 28);
  assert (M_INC(28) == 29);

  assert ((0) == M_DEC(1));
  assert ((1) == M_DEC(2));
  assert ((2) == M_DEC(3));
  assert ((3) == M_DEC(4));
  assert ((4) == M_DEC(5));
  assert ((5) == M_DEC(6));
  assert ((6) == M_DEC(7));
  assert ((7) == M_DEC(8));
  assert ((8) == M_DEC(9));
  assert ((9) == M_DEC(10));
  assert ((10) == M_DEC(11));
  assert ((11) == M_DEC(12));
  assert ((12) == M_DEC(13));
  assert ((13) == M_DEC(14));
  assert ((14) == M_DEC(15));
  assert ((15) == M_DEC(16));
  assert ((16) == M_DEC(17));
  assert ((17) == M_DEC(18));
  assert ((18) == M_DEC(19));
  assert ((19) == M_DEC(20));
  assert ((20) == M_DEC(21));
  assert ((21) == M_DEC(22));
  assert ((22) == M_DEC(23));
  assert ((23) == M_DEC(24));
  assert ((24) == M_DEC(25));
  assert ((25) == M_DEC(26));
  assert ((26) == M_DEC(27));
  assert ((27) == M_DEC(28));
  assert ((28) == M_DEC(29));

  assert( M_ADD(2,5) == M_SUB(10,3));
  assert( M_ADD(5,15) == M_SUB(25,5));

  assert(M_EQUAL(2, 2));
  assert(!M_EQUAL(1, 2));
  assert(M_EQUAL(52, 52));

  assert(!M_NOTEQUAL(2, 2));
  assert(M_NOTEQUAL(1, 2));
  assert(!M_NOTEQUAL(52, 52));

  assert(!M_LESS_THAN_P(4, 2));
  assert(!M_LESS_THAN_P(4, 3));
  assert(!M_LESS_THAN_P(4, 4));
  assert(M_LESS_THAN_P(4, 5));
  assert(M_LESS_THAN_P(4, 6));
  assert(M_LESS_THAN_P(4, 7));

  assert(M_GREATER_OR_EQUAL_P(4, 2));
  assert(M_GREATER_OR_EQUAL_P(4, 3));
  assert(M_GREATER_OR_EQUAL_P(4, 4));
  assert(!M_GREATER_OR_EQUAL_P(4, 5));
  assert(!M_GREATER_OR_EQUAL_P(4, 6));
  assert(!M_GREATER_OR_EQUAL_P(4, 7));

  assert(M_GREATER_THAN_P(4, 2));
  assert(M_GREATER_THAN_P(4, 3));
  assert(!M_GREATER_THAN_P(4, 4));
  assert(!M_GREATER_THAN_P(4, 5));
  assert(!M_GREATER_THAN_P(4, 6));
  assert(!M_GREATER_THAN_P(4, 7));

  assert(!M_LESS_OR_EQUAL_P(4, 2));
  assert(!M_LESS_OR_EQUAL_P(4, 3));
  assert(M_LESS_OR_EQUAL_P(4, 4));
  assert(M_LESS_OR_EQUAL_P(4, 5));
  assert(M_LESS_OR_EQUAL_P(4, 6));
  assert(M_LESS_OR_EQUAL_P(4, 7));

  assert(!M_KEYWORD_P(M_UNDERFLOW, M_SUB(1, 1)));
  assert(M_KEYWORD_P(M_UNDERFLOW, M_SUB(1, 2)));
  assert(M_KEYWORD_P(M_UNDERFLOW, M_SUB(1, 6)));
  assert(M_KEYWORD_P(M_UNDERFLOW, M_SUB(1, 5)));
  assert(!M_KEYWORD_P(M_OVERFLOW, M_ADD(M_MAX_NB_ARGUMENT, 1)));
  assert(M_KEYWORD_P(M_OVERFLOW, M_ADD(M_MAX_NB_ARGUMENT, 2)));
  assert(M_KEYWORD_P(M_OVERFLOW, M_ADD(M_MAX_NB_ARGUMENT, 3)));
  // Test if the initial error is not lost in translation
  assert(M_KEYWORD_P(M_UNDERFLOW, M_INC(M_UNDERFLOW)));
  assert(M_KEYWORD_P(M_OVERFLOW, M_DEC(M_OVERFLOW)));

  assert(M_AND(0, 0) == 0);
  assert(M_AND(0, 1) == 0);
  assert(M_AND(1, 0) == 0);
  assert(M_AND(1, 1) == 1);

  assert(M_OR(0, 0) == 0);
  assert(M_OR(0, 1) == 1);
  assert(M_OR(1, 0) == 1);
  assert(M_OR(1, 1) == 1);

  assert(M_AND3(0, 0, 0) == 0);
  assert(M_AND3(0, 0, 1) == 0);
  assert(M_AND3(0, 1, 0) == 0);
  assert(M_AND3(0, 1, 1) == 0);
  assert(M_AND3(1, 0, 0) == 0);
  assert(M_AND3(1, 0, 1) == 0);
  assert(M_AND3(1, 1, 0) == 0);
  assert(M_AND3(1, 1, 1) == 1);

  assert(M_OR3(0, 0, 0) == 0);
  assert(M_OR3(0, 0, 1) == 1);
  assert(M_OR3(0, 1, 0) == 1);
  assert(M_OR3(0, 1, 1) == 1);
  assert(M_OR3(1, 0, 0) == 1);
  assert(M_OR3(1, 0, 1) == 1);
  assert(M_OR3(1, 1, 0) == 1);
  assert(M_OR3(1, 1, 1) == 1);
}

static void test_return(void)
{
  assert (M_RET_ARG1(1, 0));
  assert (M_RET_ARG2(0, 1, 0));
  assert (M_RET_ARG3(0, 0, 1, 0));
  assert (M_RET_ARG27(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0));
  assert (M_GET_AT((0,1,0),1));
  assert (M_GET_AT((1,0,0),0));
  assert (M_GET_AT((0,0,0,1),3));
}

static void test_invert(void)
{
  assert (M_RET_ARG1 (M_REVERSE(0,1)));
  assert (M_RET_ARG1 (M_REVERSE(0,0,1)));
  assert (M_RET_ARG1 (M_REVERSE(0,0,0,1)));
  assert (M_RET_ARG1 (M_REVERSE(0,0,0,0,1)));
}

static void test_min(void)
{
  assert (M_MIN(1,2) == 1);
  assert (M_MIN(1,-2) == -2);
  assert (M_MAX(1,2) == 2);
  assert (M_MAX(1,-2) == 1);
}

static void test_empty(void)
{
  assert (M_EMPTY_P() == true);
  assert (M_EMPTY_P(1) == false);
  assert (M_EMPTY_P(,) == false);
  assert (M_EMPTY_P(()) == false);
  assert (M_EMPTY_P("123") == false);
  assert (M_EMPTY_P(,1,2,3) == false);
  assert (M_EMPTY_P(+2) == false);
  assert (M_EMPTY_P(-2) == false);
  assert (M_EMPTY_P( () ) == false);
#define F0() int
#define F1() (void)
#define F2(x) x
#define F3() 
#define F4() 1,2  
  assert (M_EMPTY_P(F0) == false);
#ifndef M_COMMA_P_WORKAROUND
  // Disable tests on platform with a non working preprocessor
  assert (M_EMPTY_P(F1) == false);
  assert (M_EMPTY_P(F2) == false);
  assert (M_EMPTY_P(F3) == false);
#endif
  assert (M_EMPTY_P(F4) == false);
#define G0 int
#define G1 (void)
#define G2 x
#define G3 
#define G4 1,2  
  assert (M_EMPTY_P(G0) == false);
  assert (M_EMPTY_P(G1) == false);
  assert (M_EMPTY_P(G2) == false);
  assert (M_EMPTY_P(G3) == true);
  assert (M_EMPTY_P(G4) == false);
}

#define f(n) (n)*(n) +
#define f2(d,n) (d)*(n) +
#define f3(d,i,n) (d)*(i)*(n) +

static void test_map(void)
{
  assert (M_MAP (f, 1) 0 == 1);
  assert (M_MAP (f, 1, 2) 0 == 5);
  assert (M_MAP (f, 1, 2, 3) 0 == 14);
  assert (M_MAP (f, M_SEQ(1, 4)) 0 ==  1+4+9+16);

  assert (M_MAP2 (f2, 2, 1) 0 == 2);
  assert (M_MAP2 (f2, 3, 1, 2) 0 == 9);
  assert (M_MAP2 (f2, 4, 1, 2, 3) 0 == 24);

  assert (M_MAP3 (f3, 2, 2) 0 == 4);
  assert (M_MAP3 (f3, 3, 2, 4) 0 == 30);
  assert (M_MAP3 (f3, 4, 2, 4, 6) 0 == 8+32+12*6);

  assert (M_CROSS_MAP(f2, (1), (2)) 0 == 1*2);
  assert (M_CROSS_MAP(f2, (1), (2, 3)) 0 == 1*2+1*3);
  assert (M_CROSS_MAP(f2, (1, 3), (2)) 0 == 1*2+3*2);
  assert (M_CROSS_MAP(f2, (1, 3), (2,4)) 0 == 1*2 + 1*4 + 3*2 + 3*4);
  assert (M_CROSS_MAP(f2, (1, 3, 5), (2,4)) 0 == 1*2 + 1*4 + 3*2 + 3*4 + 5*2 + 5*4);

  assert (M_CROSS_MAP2(f3, 2, (1), (2)) 0 == (1*2) * 2);
  assert (M_CROSS_MAP2(f3, 2, (1), (2, 3)) 0 == (1*2+1*3) * 2);
  assert (M_CROSS_MAP2(f3, 2, (1, 3), (2)) 0 == (1*2+3*2) * 2);
  assert (M_CROSS_MAP2(f3, 2, (1, 3), (2,4)) 0 == (1*2 + 1*4 + 3*2 + 3*4) * 2);
  assert (M_CROSS_MAP2(f3, 2, (1, 3, 5), (2,4)) 0 == (1*2 + 1*4 + 3*2 + 3*4 + 5*2 + 5*4)*2);

}

static void test_let(void)
{
  bool b = false;
  M_LET(z, M_CLASSIC_OPLIST(testobj)) {
    testobj_set_ui(z, 12);
    M_LET((zz,z), M_CLASSIC_OPLIST(testobj)) {
      assert( testobj_cmp_ui(zz, 12) == 0);
    }
    M_LET((zz,(42)), M_OPEXTEND(M_CLASSIC_OPLIST(testobj), INIT_WITH(testobj_init_set_ui))) {
      assert( testobj_cmp_ui(zz, 42) == 0);
    }
    M_LET((zz,z), z2, M_CLASSIC_OPLIST(testobj)) {
      assert( testobj_cmp_ui(zz, 12) == 0);
    }
    M_LET(z3, (zz,(42)), M_OPEXTEND(M_CLASSIC_OPLIST(testobj), INIT_WITH(testobj_init_set_ui))) {
      assert( testobj_cmp_ui(zz, 42) == 0);
    }
    b = true;
    break;
    assert(0);
  }
  assert(b);

  // Test of recursive INIT_WITH within M_LET
#define OPL1 (TYPE(int), INIT_WITH(OPL1_F))
#define OPL1_F(d, x) ((d) = (x))
#define OPL2 (TYPE(int), INIT_WITH(API_1(OPL2_F)), OPLIST(OPL1) )
#define OPL2_F(opl, d, x) M_APPLY_API(M_GET_INIT_WITH M_GET_OPLIST opl, M_GET_OPLIST opl, d, x)
#define OPL3 (TYPE(int), INIT_WITH(API_1(OPL3_F)), OPLIST(OPL2) )
#define OPL3_F(opl, d, x) M_APPLY_API(M_GET_INIT_WITH M_GET_OPLIST opl, M_GET_OPLIST opl, d, x)
#define OPL4 (TYPE(int), INIT_WITH(API_1(OPL4_F)), OPLIST(OPL3) )
#define OPL4_F(opl, d, x) M_APPLY_API(M_GET_INIT_WITH M_GET_OPLIST opl, M_GET_OPLIST opl, d, x)
#define OPL5 (TYPE(int), INIT_WITH(API_1(OPL5_F)), OPLIST(OPL4) )
#define OPL5_F(opl, d, x) M_APPLY_API(M_GET_INIT_WITH M_GET_OPLIST opl, M_GET_OPLIST opl, d, x)
#define OPL6 (TYPE(int), INIT_WITH(API_1(OPL6_F)), OPLIST(OPL5) )
#define OPL6_F(opl, d, x) M_APPLY_API(M_GET_INIT_WITH M_GET_OPLIST opl, M_GET_OPLIST opl, d, x)
  
  M_LET( (x, 4585) , OPL1)
    assert (x == 4585);
  M_LET( (x, 4586) , OPL2)
    assert (x == 4586);
  M_LET( (x, 4587) , OPL3)
    assert (x == 4587);
  M_LET( (x, 4588) , OPL4)
    assert (x == 4588);
  M_LET( (x, 4589) , OPL5)
    assert (x == 4589);
  M_LET( (x, 4590) , OPL6)
    assert (x == 4590);
    
  // Test of M_LET_IF
  int c = 0;
  M_LET_IF( assert(c++ == 0), (assert(c++ == 1), true), assert(c++ == 3)) {
    assert(c++ == 2);
  }
  M_LET_IF( assert(c++ == 4), (assert(c++ == 5), false), assert(0)) {
    assert(0);
  }
  assert(c++ == 6);
  M_LET_IF(int *p = (int*) malloc(sizeof(int)), p!=0, free(p)) {
    assert(p != NULL);
    *p = 4;
  }

  M_LET_IF( assert(c++ == 7), (assert(c++ == 8), true), assert(c++ == 10), assert(0) ) {
    assert(c++ == 9);
  }
  M_LET_IF( assert(c++ == 11), (assert(c++ == 12), false), assert(0), assert(c++ == 13) ) {
    assert(0);
  }
  assert(c++ == 14);

  M_LET_IF( assert(c++ == 15), (assert(c++ == 16), true), assert(c++ == 18) ) {
    assert(c++ == 17);
    break;
    assert(0);
  }
  assert(c++ == 19);

  c = 7;
  int *p = (int*)malloc(sizeof(int));
  assert(p != NULL);
  M_DEFER ( (assert(c++ == 8), free(p)) ) {
    *p = 4;
    assert(c++ == 7);
  }
  assert(c++ == 9);
  M_DEFER ( assert(c++ == 11) ) {
    assert(c++ == 10);
    break;
    assert(0);
  }
  assert(c++ == 12);
}

static void test_va(void)
{
  assert (M_RET_ARG1 (M_VA(a),zz) == 1);
  assert (M_RET_ARG2 (M_VA(17),zz) == 17);
  assert (M_RET_ARG1 (M_VA(a,b,c),zz) == 3);
  assert (M_RET_ARG2 (M_VA(17,42,59),zz) == 17);
}

static void test_if(void)
{
  assert (M_IF(1)(true, false));
  assert (!M_IF(0)(true, false));
  assert (M_IF(x)(true, false));
}

static void test_test(void)
{
  assert(M_COMMA_P(,));
  assert(M_COMMA_P(1,2));
  assert(M_COMMA_P(1,2,aa));
  assert(!M_COMMA_P(1));
  assert(!M_COMMA_P(()));
  assert(!M_COMMA_P("aa"));
  assert(!M_COMMA_P(""));
  assert(!M_COMMA_P((,)));

  assert(M_PARENTHESIS_P(()));
  assert(M_PARENTHESIS_P((aa)));
  assert(M_PARENTHESIS_P((aa,bb)));
  assert(!M_PARENTHESIS_P(a()));
  assert(!M_PARENTHESIS_P(a(ada)));
  assert(!M_PARENTHESIS_P(a,b));
  assert(!M_PARENTHESIS_P(a));
  assert(!M_PARENTHESIS_P());
  assert(!M_PARENTHESIS_P(,));
  assert(!M_PARENTHESIS_P((),));
  assert(!M_PARENTHESIS_P(()x));

  assert(!M_KEYWORD_P(sum, sul));
  assert(M_KEYWORD_P(sum, sum));
  assert(M_KEYWORD_P(sum, add));
  assert(M_KEYWORD_P(product, product));
  assert(M_KEYWORD_P(product, mul));
  assert(!M_KEYWORD_P(product, mul2));
  assert(M_KEYWORD_P(_, _));
  assert(M_KEYWORD_P(_, _() ));

  assert(M_KEYWORD_P(LIST, LIST));
  assert(M_KEYWORD_P(LIST, LIST(1,23,4) ));
  assert(!M_KEYWORD_P(LIST, SEQ(1,23,4) ));

  assert(M_KEYWORD_TO_VA_ARGS(LIST, LIST(16)) == 16);
  assert(M_KEYWORD_TO_VA_ARGS(LIST, LIST(14)) == 14);
  assert(M_GET_AT( ( M_KEYWORD_TO_VA_ARGS(LIST, LIST(11, 22, 33)) ), 0) == 11);
  assert(M_GET_AT( ( M_KEYWORD_TO_VA_ARGS(LIST, LIST(11, 22, 33)) ), 1) == 22);
  assert(M_GET_AT( ( M_KEYWORD_TO_VA_ARGS(LIST, LIST(11, 22, 33)) ), 2) == 33);
  assert(M_KEYWORD_TO_VA_ARGS(_, _(10)) == 10);
}

static void test_list(void)
{
  assert(M_KEEP_ARGS(1, 1, 0, 0, 0, 0));
  assert(M_SKIP_ARGS(4, 0, 0, 0, 0, 1));
  assert(M_MID_ARGS(2, 1, 0, 0, 1, 0));

  const int tab[] = { M_REPLICATE_C(5, 1) };
  assert(sizeof tab == 5 * sizeof (int) );
  for(int i = 0; i < 5; i++) assert(tab[i] == 1);
  const char str[] = M_REPLICATE(10, "N");
  assert(strcmp(str, "NNNNNNNNNN") == 0);

  assert(M_EQUAL(M_REMOVE_PARENTHESIS( 17 ), 17));
  assert(M_EQUAL(M_REMOVE_PARENTHESIS( (17) ), 17));

  assert( M_FILTER(M_EQUAL, 17, 1, 42, 17, 89) );
  assert( M_FILTER_C(M_EQUAL, 17, 1, 42, 17, 89) );
  const int tab2[] = { M_FILTER_C(M_NOTEQUAL, 8, 1, 3, 4, 8, 9, 8, 10) };
  assert(sizeof tab2 == 5 * sizeof (int) );
  const int tabref[] = { 1, 3, 4, 9, 10 };
  assert (sizeof tab2 == sizeof tabref);
  for(int i = 0; i < 5 ; i++) assert(tab2[i] == tabref[i]);

#define TEST_MUL(a) M_PAIR_1 a * M_PAIR_2 a
#define TEST_ADD(a,b) a + b
  int red = M_REDUCE(TEST_MUL, TEST_ADD, M_OPFLAT M_MERGE_ARGLIST( (1, 2, 3), (4, 5, 6) ) );
  assert(red ==  1 * 4 + 2 * 5 + 3 * 6);
  red = M_REDUCE(TEST_MUL, TEST_ADD, M_OPFLAT M_MERGE_ARGLIST( (1), (4) ) );
  assert(red ==  1 * 4);
  red = M_REDUCE(TEST_MUL, TEST_ADD, M_OPFLAT M_MERGE_ARGLIST( (3, 5), (7, 11) ) );
  assert(red ==  3 * 7 + 5 * 11);

  red = M_REDUCE(M_ID, TEST_ADD, M_OPFLAT M_CAT_ARGLIST( , (3)));
  assert(red == 3);
  red = M_REDUCE(M_ID, TEST_ADD, M_OPFLAT M_CAT_ARGLIST((5, 4), ));
  assert(red == 5 + 4);
  red = M_REDUCE(M_ID, TEST_ADD, M_OPFLAT M_CAT_ARGLIST((5, 4), ( 9, 25, 111) ));
  assert(red == 5 + 4 + 9 + 25 + 111);

}

static void test_oplist(void)
{
#define TEST_OPLIST(op, oplist)                  \
  M_C(M_GET_, op) oplist

  assert (TEST_OPLIST(INIT, (INIT(true))));
  assert (TEST_OPLIST(INIT, (INIT(true),CLEAR(false))));
  assert (TEST_OPLIST(INIT, (INIT_SET(false), INIT(true),CLEAR(false))));
  assert (TEST_OPLIST(INIT, (INIT_SET(false), INIT(true))));

  assert (M_TEST_METHOD_P(INIT, (INIT(empty))));
  assert (M_TEST_METHOD_P(INIT, (INIT(empty),CLEAR(empty))));
  assert (M_TEST_METHOD_P(INIT, (INIT_SET(empty), INIT(empty),CLEAR(empty))));
  assert (M_TEST_METHOD_P(INIT, (INIT_SET(empty), INIT(empty))));
  assert (M_TEST_METHOD_P(OPLIST, (INIT_SET(empty), INIT(empty), OPLIST(a(INIT(empty), CLEAR(empty))), CLEAR(empty))));
  // True oplist are not supported for TEST_METHOD_P + OPLIST...
  assert (M_TEST_METHOD_P(OPLIST, (INIT_SET(empty), OPLIST(a(INIT(empty), CLEAR(empty))))));
  assert (M_TEST_METHOD_P(OPLIST, (INIT_SET(empty), M_IF_METHOD(CLEAR, (INIT(empty), CLEAR(empty)))(OPLIST(empty),))));

  assert (!M_TEST_METHOD_P(INIT2, (INIT(empty))));
  assert (!M_TEST_METHOD_P(INIT2, (INIT(empty),CLEAR(empty))));
  assert (!M_TEST_METHOD_P(INIT2, (INIT_SET(empty), INIT(empty),CLEAR(empty))));
  assert (!M_TEST_METHOD_P(INIT2, (INIT_SET(empty), INIT(empty))));

#define CHECK_OPLIST(op, oplist)                \
  M_IF_METHOD(op, oplist) (true, false)

  assert (CHECK_OPLIST (INIT, M_BASIC_OPLIST));
  assert (CHECK_OPLIST (INIT_SET, M_BASIC_OPLIST));
  assert (CHECK_OPLIST (SET, M_BASIC_OPLIST));
  assert (CHECK_OPLIST (CLEAR, M_BASIC_OPLIST));
  assert (!CHECK_OPLIST (CLEAR_TOOT, M_BASIC_OPLIST));

  assert (M_OPLIST_P(()));
  assert (M_OPLIST_P((INIT(init))));
  assert (M_OPLIST_P((INIT(init),CLEAR(clear))));
  assert (M_OPLIST_P((INIT(init),CLEAR(clear),)));
  assert (!M_OPLIST_P());
  assert (!M_OPLIST_P((())));
  assert (!M_OPLIST_P(INIT(init)));
  assert (!M_OPLIST_P((INIT(init)) a));
  assert (!M_OPLIST_P(func(INIT(init)) a));
  assert (!M_OPLIST_P(func(INIT(init),) a));

#define aop (INIT(0), CLEAR(clear))
  assert (M_TEST_DISABLED_METHOD_P(INIT, aop));
  assert (!M_TEST_DISABLED_METHOD_P(INIT2, aop));
  assert (!M_TEST_DISABLED_METHOD_P(CLEAR, aop));
  assert (!M_TEST_DISABLED_METHOD_P(INIT, ()));

#define M_OPL_op() (INIT(1), CLEAR(0))
  assert(M_ID (M_GET_INIT M_GLOBAL_OPLIST(op)));         
  assert(M_ID (!M_GET_INIT M_GLOBAL_OPLIST(aop)));

#define MAKE_OPLIST(op) (OPLIST(M_GLOBAL_OPLIST_OR_DEF(op)()))
#define M_OPL_uint() (INIT(0), CLEAR(1))
#define M_OPL_array_uint() (OPLIST(M_GLOBAL_OPLIST_OR_DEF(uint)()))
#define M_OPL_list_array_uint() MAKE_OPLIST(array_uint)
#define A1     A2(M_OPL_list_array_uint())
#define A2(op) A3(M_GET_OPLIST op)
#define A3(op) A4(M_GET_OPLIST op)
#define A4(op) M_GET_CLEAR op
  assert (A1);

#define A5   (NAME(1))
  assert (M_ID (M_GET_NAME A5));

#define A6  (NAME(A6), INIT_WITH(API_1(M_INIT_WITH_NVAR)))
#define A6_init_with__1(x) 1
#define A6_init_with__2(x, y) 0
  assert (M_ID(M_CALL_INIT_WITH (A6, 0)));
  assert (M_ID(!M_CALL_INIT_WITH (A6, 0, 0)));
}

static void test_cast(void)
{
  float f1 = 1.0;
  float f2 = M_ASSIGN_CAST(float, f1);
  int   i1 = M_ASSIGN_CAST(int, f2);
  assert (i1 == 1);
  int *p1 = &i1;
  const int *p2 = M_CONST_CAST(int, p1);
  assert (*p2 == 1);
}

static void test_reduce(void)
{
#define id(x,a) a
#define add(a,b) a + b
  assert ( M_REDUCE2(id, add, d, M_SEQ(1, 5)) == 5 * 6 /2);
  assert ( M_REDUCE2(id, add, d, M_SEQ(1, 10)) == 10 * 11 /2);

  assert ( M_REDUCE2(id, add, d, M_SEQ(1, 1)) == 1);
  assert ( M_REDUCE2(id, add, d, M_SEQ(1, 2)) == 1+2);

  assert ( M_REDUCE(M_ID, add, M_SEQ(1, 5))
           + M_REDUCE(M_ID, add, M_SEQ(6, 10))
           == M_REDUCE(M_ID, add, M_SEQ(1, 10)));                    

#define add3(a,b,c) (a + b + c)
#define mul(a,b) (a) * (b)
  int d = 42;
  assert ( M_REDUCE3(add3, mul, d, 20, 4, 5) == 
          (d + 1 + 20) * ( 
            (d + 2 + 4) * (
              (d + 3 + 5)
            )
          )
  );

}

static void test_as_type(void)
{
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
  int i = 42;
  float f = 2.5;
  void *p = (void*) &i;
  struct as_type { int n; } n = { 17 };
  
  assert (M_AS_TYPE(int, i) == 42);
  assert (M_AS_TYPE(float, i) == 0.0);
  assert (M_AS_TYPE(void *, i) == 0);
  assert (M_AS_TYPE(struct as_type, i).n == 0 );

  assert (M_AS_TYPE(int, f) == 0);
  assert (M_AS_TYPE(float, f) == 2.5);
  assert (M_AS_TYPE(void *, f) == 0);
  assert (M_AS_TYPE(struct as_type, f).n == 0 );

  assert (M_AS_TYPE(int, p) == 0);
  assert (M_AS_TYPE(float, p) == 0.0);
  assert (M_AS_TYPE(void *, p) == &i);
  assert (M_AS_TYPE(struct as_type, p).n == 0 );

  assert (M_AS_TYPE(int, n) == 0);
  assert (M_AS_TYPE(float, n) == 0.0);
  assert (M_AS_TYPE(void *, n) == 0);
  assert (M_AS_TYPE(struct as_type, n).n == 17 );
#endif
}

static void test_parse_standard_c_type(void)
{
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
  char c = 0;
  const char *endp;
  M_PARSE_DEFAULT_TYPE(&c, "C", NULL);
  assert (c == 'C');
  M_PARSE_DEFAULT_TYPE(&c, "D", &endp);
  assert (c == 'D');
  assert (*endp == 0);

  bool b = false;
  M_PARSE_DEFAULT_TYPE(&b, "1", NULL);
  assert (b == true);
  M_PARSE_DEFAULT_TYPE(&b, "0", &endp);
  assert (b == false);
  assert (*endp == 0);

  short s = 0;
  M_PARSE_DEFAULT_TYPE(&s, "-2", NULL);
  assert (s == -2);
  M_PARSE_DEFAULT_TYPE(&s, "3", &endp);
  assert (s == 3);
  assert (*endp == 0);

  int i = 0;
  M_PARSE_DEFAULT_TYPE(&i, "2", NULL);
  assert (i == 2);
  M_PARSE_DEFAULT_TYPE(&i, "-3", &endp);
  assert (i == -3);
  assert (*endp == 0);
  
  long l  = 0;
  M_PARSE_DEFAULT_TYPE(&l, "1742", NULL);
  assert (l == 1742);
  M_PARSE_DEFAULT_TYPE(&l, "-421742", &endp);
  assert (l == -421742);
  assert (*endp == 0);
  
  long long ll  = 0;
  M_PARSE_DEFAULT_TYPE(&ll, "-1742548676843540", NULL);
  assert (ll == -1742548676843540LL);
  M_PARSE_DEFAULT_TYPE(&ll, "1742548676843541", &endp);
  assert (ll == 1742548676843541LL);
  assert (*endp == 0);

  unsigned short us = 0;
  M_PARSE_DEFAULT_TYPE(&us, "3", NULL);
  assert (us == 3);
  M_PARSE_DEFAULT_TYPE(&us, "5", &endp);
  assert (us == 5);
  assert (*endp == 0);

  unsigned int ui = 0;
  M_PARSE_DEFAULT_TYPE(&ui, "2", NULL);
  assert (ui == 2);
  M_PARSE_DEFAULT_TYPE(&ui, "25", &endp);
  assert (ui == 25);
  assert (*endp == 0);
  
  unsigned long ul  = 0;
  M_PARSE_DEFAULT_TYPE(&ul, "1742", NULL);
  assert (ul == 1742);
  M_PARSE_DEFAULT_TYPE(&ul, "17412", &endp);
  assert (ul == 17412);
  assert (*endp == 0);
  
  unsigned long long ull  = 0;
  M_PARSE_DEFAULT_TYPE(&ull, "1742548676843540", NULL);
  assert (ull == 1742548676843540ULL);
  M_PARSE_DEFAULT_TYPE(&ull, "1742548676843541", &endp);
  assert (ull == 1742548676843541ULL);
  assert (*endp == 0);
  
  float f;
  M_PARSE_DEFAULT_TYPE(&f, "-0.5", NULL);
  assert (f == -0.5);
  M_PARSE_DEFAULT_TYPE(&f, "4.5", &endp);
  assert (f == 4.5);
  assert (*endp == 0);

  double d;
  M_PARSE_DEFAULT_TYPE(&d, "2.5", NULL);
  assert (d == 2.5);
  M_PARSE_DEFAULT_TYPE(&d, "-3.5", &endp);
  assert (d == -3.5);
  assert (*endp == 0);
  
  long double ld;
  M_PARSE_DEFAULT_TYPE(&ld, "2.5", NULL);
  assert (ld == 2.5);
  M_PARSE_DEFAULT_TYPE(&ld, "-5.5", &endp);
  assert (ld == -5.5);
  assert (*endp == 0);
#endif
}

typedef int ti[1];
typedef struct { int b,c; } ts;

static void f_ti(ti x)
{
  ti y;
  M_MOVE_A1_DEFAULT(y,x);
  assert(y[0] == 9);
  assert(x[0] == 0);
}

static void f_ts(ts x)
{
  ts y;
  M_MOVE_DEFAULT(y,x);
  assert(y.b == 2);
  assert(y.c == 3);
  assert(x.b == 0);
  assert(x.c == 0);
}

static void f_ts2(ts *x)
{
  ts y;
  M_MOVE_DEFAULT(y,*x);
  assert(y.b == 2);
  assert(y.c == 3);
  assert(x->b == 0);
  assert(x->c == 0);
}

static void test_move_default(void)
{
  int o, p;
  o = 9;
  M_MOVE_DEFAULT(p, o);
  assert(p == 9);
  assert(o == 0);

  double u, k;
  k = -1.0;
  u = 9.5;
  M_MOVE_DEFAULT(k, u);
  assert(k == 9.5);
  assert(u == 0.0); // NOTE: memset(0) sets an IEEE-754 number to 0.0

  int *ptr = &o, *m;
  M_MOVE_DEFAULT(m, ptr);
  assert( ptr == NULL);
  assert( m == &o);
  
  ti x, y;
  x[0] = 9;
  M_MOVE_A1_DEFAULT(y, x);
  assert(y[0] == 9);
  assert(x[0] == 0);
  
  x[0] = 9;
  f_ti(x);
  assert(x[0] == 0);
  
  ts y2, x2 = {  2, 3};
  M_MOVE_DEFAULT(y2, x2);
  assert(y2.b == 2 && y2.c == 3);
  assert(x2.b == 0 && x2.c == 0);

  x2.b = 2; x2.c = 3;
  f_ts(x2);
  assert(x2.b == 2);
  assert(x2.c == 3);

  f_ts2(&x2);
  assert(x2.b == 0);
  assert(x2.c == 0);
}

static void test_builtin(void)
{
  assert(m_core_clz32(0) == 32);
  for(unsigned i = 0; i < 31; i++) {
    assert(m_core_clz32((uint32_t) (1UL<<i)) == 31U - i);
    assert(m_core_clz32((uint32_t) ((1UL<<i) | 1)) == 31U - i);
  }

  assert(m_core_clz64(0) == 64);
  for(unsigned i = 0; i < 63; i++) {
    assert(m_core_clz64((uint64_t) (1ULL<<i)) == 63U - i);
    assert(m_core_clz64((uint64_t) ((1ULL<<i) | 1)) == 63U - i);
  }

  assert(m_core_ctz32(0) == 32);
  for(unsigned i = 0; i < 31; i++) {
    assert(m_core_ctz32((uint32_t) (1UL<<i)) == i);
    assert(m_core_ctz32((uint32_t) ((1UL<<i) | 1UL<<31)) == i);
  }

  assert(m_core_ctz64(0) == 64);
  for(unsigned i = 0; i < 63; i++) {
    assert(m_core_ctz64((uint64_t) (1ULL<<i)) == i);
    assert(m_core_ctz64((uint64_t) ((1ULL<<i) | 1ULL<<63)) == i);
  }

  assert ( m_core_roundpow2(0) == 0);
  for(unsigned i = 0; i < 62; i++) {
    assert ( m_core_roundpow2(1ULL<<i) == 1ULL<<i);
    assert ( m_core_roundpow2((1ULL<<i)+1) == 1ULL<<(i+1) );
  }

  for(unsigned i = 0; i < 3000; i++) {
    assert( m_core_rotl32a(i, 1) == i * 2);
    assert( m_core_rotl32a(i, 2) == i * 4);
    assert( m_core_rotl32a((uint32_t) ((1UL<<31) + i), 1) == i * 2 + 1);
    assert( m_core_rotl32a((uint32_t) ((1UL<<31) + i), 2) == i * 4 + 2);
  }

  for(unsigned i = 0; i < 3000; i++) {
    assert( m_core_rotl64a(i, 1) == i * 2);
    assert( m_core_rotl64a(i, 2) == i * 4);
    assert( m_core_rotl64a((1ULL<<63) + i, 1) == i * 2 + 1);
    assert( m_core_rotl64a((1ULL<<63) + i, 2) == i * 4 + 2);
  }
}

const char str1[] = "A";
const char str2[] = "AB";
const char str3[] = "Hello";

static void test_str_hash(void)
{
  assert (M_CALL_HASH(M_CSTR_OPLIST, str1) != 0);
  assert (M_CALL_HASH(M_CSTR_OPLIST, str2) != 0);
  assert (M_CALL_HASH(M_CSTR_OPLIST, str3) != 0);
}

static void test_M_CSTR(void)
{
  int r;
  r = strcmp(M_CSTR("Len=%d", 17), "Len=17");
  assert (r == 0);
  r = strcmp(M_CSTR("Hello %s %c", "World", '!'), "Hello World !");
  assert (r == 0);

  // Reduce allocation to test truncation
#undef M_USE_CSTR_ALLOC
#define M_USE_CSTR_ALLOC 8
  // Disable GNUC warning as we want to test this behavior.
  // this warning is supported by CLANG
#if (defined (__GNUC__) && __GNUC__ >= 7 && !defined(__clang__)) \
  ||(defined(__clang__) && __clang_major__ >= 18)
  _Pragma("GCC diagnostic push")
  _Pragma("GCC diagnostic ignored \"-Wformat-truncation\"")
#endif
  r = strcmp(M_CSTR("Hello %s %c", "World", '!'), "Hello W");
#if (defined (__GNUC__) && __GNUC__ >= 7 && !defined(__clang__)) \
  ||(defined(__clang__) && __clang_major__ >= 18)
  _Pragma("GCC diagnostic pop")
#endif
  assert (r == 0);
}

static void test_properties(void)
{
#define OP_PROP() ( INIT(init), PROPERTIES((INIT(1), IT_TYPE(2), INIT_WITH(priority))) )
  assert( M_GET_PROPERTY (OP_PROP(), INIT ) == 1);
  assert( M_GET_PROPERTY (OP_PROP(), CLEAR ) == 0);
  assert( M_GET_PROPERTY (OP_PROP(), IT_TYPE ) == 2);
  assert( M_KEYWORD_P( M_GET_PROPERTY (OP_PROP(), INIT_WITH ), priority));
}

static int init3(int *p, int a, int b)
{
  *p = b;
  return a;
}

static int cmp1(int a, int b)
{
  return a < b ? -1 : a > b;
}

static int cmp2(int *a, int *b)
{
  return *a < *b ? -1 : *a > *b;
}

#define cmp3(a, b)                              \
  (a < b ? -1 : a > b)

static void test_generic_api(void)
{
  assert( M_HEAD(17, 0) == 17);
  assert( M_TAIL(18, 19) == 19);
  assert( M_HEAD_2(18, 17, 0) == 17);
  assert( M_TAIL_2(18, 17, 8) == 8);

#define GO1 (INIT(API( init3, ARG1, ARGPTR2, 17, ID(-43) )))
#define GO2 (INIT(API( init3, NONE, ARGPTR2, 0, ARG1 )))
#define GO3 (EQUAL(API( cmp2, EQ(0), ARGPTR1, ARGPTR2 )))
#define GO4 (EQUAL(API( cmp1, EQ(0), ARG1, ARG2 )))
#define GO5 (EQUAL(API( cmp3, EQ(0), ARG1, ARG2 )))
#define GO6 (CMP(API(cmp3, NEG, ARG1, ARG2)))

  int x = 0, y = 0;
  M_CALL_INIT(GO1, x, y);
  assert(x == 17);
  assert(y == -43);

  x = 89;
  M_CALL_INIT(GO2, x, y);
  assert( y == 89);
  assert( x == 89);

  x = y = 78;
  bool b = M_CALL_EQUAL(GO3, x, y);
  assert(b);
  x = 77;
  b = M_CALL_EQUAL(GO4, x, y);
  assert(!b);

  x = y = 78;
  b = M_CALL_EQUAL(GO5, x, y);
  assert(b);
  x = 77;
  b = M_CALL_EQUAL(GO5, x, y);
  assert(!b);

  x = 78;
  y = 77;
  int r = M_CALL_CMP(GO6, x, y);
  assert(r < 0);
  x = 76;
  y = 77;
  r = M_CALL_CMP(GO6, x, y);
  assert(r > 0);
  x = 76;
  y = 76;
  r = M_CALL_CMP(GO6, x, y);
  assert(r == 0);

}

static void f_default(int a, int b, int c)
{
  assert( a == 1 );
  assert( b == 2 );
  assert( c == 3 );
}

static void test_default(void)
{

#define f11(...) f_default(M_DEFAULT_ARGS(3, (1, 2, 3), __VA_ARGS__))
 f11(1, 2, 3);
 f11(1, 2);
 f11(1);

#define f12(...) f_default(M_DEFAULT_ARGS(3, (2, 3), __VA_ARGS__))
 f12(1, 2, 3);
 f12(1, 2);
 f12(1);

#define f13(...) f_default(M_DEFAULT_ARGS(3, (3), __VA_ARGS__))
 f13(1, 2, 3);
 f13(1, 2);
}

int main(void)
{
  test_cat();
  test_power2();
  test_incdec();
  test_return();
  test_invert();
  test_min();
  test_empty();
  test_list();
  test_map();
  test_let();
  test_va();
  test_if();
  test_test();
  test_oplist();
  test_cast();
  test_reduce();
  test_as_type();
  test_parse_standard_c_type();
  test_move_default();
  test_builtin();
  test_str_hash();
  test_M_CSTR();
  test_properties();
  test_generic_api();
  test_default();
  testobj_final_check();
  exit(0);
}
