#include <assert.h>
#include <stdio.h>

#include "binaryen-c.h"

// globals: address 4 is index
// decisions are at address 8+

int main() {
  BinaryenModuleRef module = BinaryenModuleCreate();

  // check()

  // if the end, halt
  BinaryenExpressionRef halter = BinaryenIf(module,
    BinaryenBinary(module,
      BinaryenGeUInt32(),
      BinaryenLoad(module, 4, 0, 0, 0, BinaryenTypeInt32(),
                   BinaryenConst(module, BinaryenLiteralInt32(4))),
      BinaryenConst(module, BinaryenLiteralInt32(4 * 27)) // jumps of 4 bytes
    ),
    BinaryenUnreachable(module),
    NULL
  );
  // increment index
  BinaryenExpressionRef incer = BinaryenStore(module,
    4, 0, 0,
    BinaryenConst(module, BinaryenLiteralInt32(4)),
    BinaryenBinary(module,
      BinaryenAddInt32(),
      BinaryenLoad(module, 4, 0, 0, 0, BinaryenTypeInt32(),
                   BinaryenConst(module, BinaryenLiteralInt32(4))),
      BinaryenConst(module, BinaryenLiteralInt32(4))
    ),
    BinaryenTypeInt32()
  );

  // optionally, print the return value
  BinaryenExpressionRef args[] = {
    BinaryenBinary(module,
      BinaryenSubInt32(),
      BinaryenConst(module, BinaryenLiteralInt32(0)),
      BinaryenLoad(module,
        4, 0, 4, 0, BinaryenTypeInt32(),
        BinaryenLoad(module, 4, 0, 0, 0, BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4)))
      )
    )
  };
  BinaryenExpressionRef debugger;
  if (1) debugger = BinaryenCall(module, "print", args, 1,
                                 BinaryenTypeNone());
  else debugger = BinaryenNop(module);

  // return the decision. need to subtract 4 that we just added,
  // and add 8 since that's where we start, so overall offset 4
  BinaryenExpressionRef returner = BinaryenLoad(module,
    4, 0, 4, 0, BinaryenTypeInt32(),
    BinaryenLoad(module, 4, 0, 0, 0, BinaryenTypeInt32(),
    BinaryenConst(module, BinaryenLiteralInt32(4)))
  );
  BinaryenExpressionRef checkBodyList[] = { halter, incer, debugger,
                                            returner };
  BinaryenExpressionRef checkBody = BinaryenBlock(module,
    NULL, checkBodyList, sizeof(checkBodyList) / sizeof(BinaryenExpressionRef),
    BinaryenTypeInt32()
  );
  BinaryenAddFunction(module,
                      "check",
                      BinaryenTypeNone(),
                      BinaryenTypeInt32(),
                      NULL,
                      0,
                      checkBody);

  // contents of main() begin here

  RelooperRef relooper = RelooperCreate(module);


  RelooperBlockRef b0;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b0 = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()));

  }

  RelooperBlockRef b1;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b1 = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()));

  }

  RelooperBlockRef b2;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b2 = RelooperAddBlockWithSwitch(relooper,
      BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()),
      BinaryenBinary(module,
        BinaryenRemUInt32(),
        BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
        BinaryenConst(module, BinaryenLiteralInt32(2))
      )
    );

  }

  RelooperBlockRef b3;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b3 = RelooperAddBlockWithSwitch(relooper,
      BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()),
      BinaryenBinary(module,
        BinaryenRemUInt32(),
        BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
        BinaryenConst(module, BinaryenLiteralInt32(1))
      )
    );

  }

  RelooperBlockRef b4;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(4))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b4 = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()));

  }

  RelooperBlockRef b5;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b5 = RelooperAddBlockWithSwitch(relooper,
      BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()),
      BinaryenBinary(module,
        BinaryenRemUInt32(),
        BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
        BinaryenConst(module, BinaryenLiteralInt32(1))
      )
    );

  }

  RelooperBlockRef b6;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(6))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b6 = RelooperAddBlockWithSwitch(relooper,
      BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()),
      BinaryenBinary(module,
        BinaryenRemUInt32(),
        BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
        BinaryenConst(module, BinaryenLiteralInt32(3))
      )
    );

  }

  RelooperBlockRef b7;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b7 = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()));

  }

  RelooperBlockRef b8;
  {
    BinaryenExpressionRef args[] = {
      BinaryenConst(module, BinaryenLiteralInt32(0))
    };
    BinaryenExpressionRef list[] = {
      BinaryenCall(module, "print", args, 1, BinaryenTypeNone()),
      BinaryenLocalSet(module, 0, BinaryenCall(module, "check", NULL, 0,
                                               BinaryenTypeInt32()))
    };

    b8 = RelooperAddBlock(relooper, BinaryenBlock(module, NULL, list, 2, BinaryenTypeNone()));

  }

  RelooperAddBranch(
    b0,
    b1,
    NULL,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b1,
    b1,
    NULL,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
      BinaryenTypeInt32()));

  {
    BinaryenIndex values[] = { 0,2,4,6,8,10,12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,42,44,46,48,50,52,54,56,58,60,62,64,66,68,70,72,74,76,78,80,82,84,86 };
    RelooperAddBranchForSwitch(
      b2,
      b7,
      values,
      sizeof(values) / sizeof(BinaryenIndex),
      BinaryenStore(
        module,
        4,
        0,
        0,
        BinaryenConst(module, BinaryenLiteralInt32(4)),
        BinaryenBinary(
          module,
          BinaryenAddInt32(),
          BinaryenLoad(module,
                       4,
                       0,
                       0,
                       0,
                       BinaryenTypeInt32(),
                       BinaryenConst(module, BinaryenLiteralInt32(4))),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 6))),
        BinaryenTypeInt32()));
  }

  RelooperAddBranchForSwitch(
    b2,
    b4,
    NULL,
    0,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
      BinaryenTypeInt32()));

  RelooperAddBranchForSwitch(
    b3,
    b6,
    NULL,
    0,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b4,
    b7,
    BinaryenBinary(
      module,
      BinaryenEqInt32(),
      BinaryenBinary(module,
                     BinaryenRemUInt32(),
                     BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
                     BinaryenConst(module, BinaryenLiteralInt32(2))),
      BinaryenConst(module, BinaryenLiteralInt32(0))),
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b4,
    b3,
    NULL,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
      BinaryenTypeInt32()));

  RelooperAddBranchForSwitch(
    b5,
    b1,
    NULL,
    0,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
      BinaryenTypeInt32()));

  {
    BinaryenIndex values[] = { 0,3,6,9,12,15,18,21,24,27,30,33,36,39,42,45,48,51,54,57,60,63,66,69,72,75,78,81,84,87,90,93,96,99,102,105,108 };
    RelooperAddBranchForSwitch(
      b6,
      b1,
      values,
      sizeof(values) / sizeof(BinaryenIndex),
      BinaryenStore(
        module,
        4,
        0,
        0,
        BinaryenConst(module, BinaryenLiteralInt32(4)),
        BinaryenBinary(
          module,
          BinaryenAddInt32(),
          BinaryenLoad(module,
                       4,
                       0,
                       0,
                       0,
                       BinaryenTypeInt32(),
                       BinaryenConst(module, BinaryenLiteralInt32(4))),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
        BinaryenTypeInt32()));
  }

  {
    BinaryenIndex values[] = { 1,4,7,10,13,16,19,22,25,28,31,34,37,40,43,46,49,52,55,58,61,64,67,70,73,76,79,82,85,88,91,94,97,100,103,106,109,112,115,118,121,124,127,130,133,136,139,142,145,148,151,154,157,160 };
    RelooperAddBranchForSwitch(
      b6,
      b7,
      values,
      sizeof(values) / sizeof(BinaryenIndex),
      BinaryenStore(
        module,
        4,
        0,
        0,
        BinaryenConst(module, BinaryenLiteralInt32(4)),
        BinaryenBinary(
          module,
          BinaryenAddInt32(),
          BinaryenLoad(module,
                       4,
                       0,
                       0,
                       0,
                       BinaryenTypeInt32(),
                       BinaryenConst(module, BinaryenLiteralInt32(4))),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
        BinaryenTypeInt32()));
  }

  RelooperAddBranchForSwitch(
    b6,
    b2,
    NULL,
    0,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b7,
    b5,
    BinaryenBinary(
      module,
      BinaryenEqInt32(),
      BinaryenBinary(module,
                     BinaryenRemUInt32(),
                     BinaryenLocalGet(module, 0, BinaryenTypeInt32()),
                     BinaryenConst(module, BinaryenLiteralInt32(2))),
      BinaryenConst(module, BinaryenLiteralInt32(0))),
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 1))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b7,
    b1,
    NULL,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
      BinaryenTypeInt32()));

  RelooperAddBranch(
    b8,
    b8,
    NULL,
    BinaryenStore(
      module,
      4,
      0,
      0,
      BinaryenConst(module, BinaryenLiteralInt32(4)),
      BinaryenBinary(
        module,
        BinaryenAddInt32(),
        BinaryenLoad(module,
                     4,
                     0,
                     0,
                     0,
                     BinaryenTypeInt32(),
                     BinaryenConst(module, BinaryenLiteralInt32(4))),
        BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
      BinaryenTypeInt32()));

  BinaryenExpressionRef body = RelooperRenderAndDispose(relooper, b0, 1);

  int decisions[] = { 5, 111, 119, 17, 179, 41, 32, 3, 171, 126, 13, 95, 70, 91, 9, 140, 99, 161, 38, 87, 153, 117, 140, 11, 157, 48, 4 };
  int numDecisions = sizeof(decisions)/sizeof(int);

  // write out all the decisions, then the body of the function
  BinaryenExpressionRef full[numDecisions + 1];

  {
    int i;
    for (i = 0; i < numDecisions; i++) {
      full[i] = BinaryenStore(module,
        4, 0, 0,
        BinaryenConst(module, BinaryenLiteralInt32(8 + 4 * i)),
        BinaryenConst(module, BinaryenLiteralInt32(decisions[i])),
        BinaryenTypeInt32()
      );
    }
  }
  full[numDecisions] = body;
  BinaryenExpressionRef all = BinaryenBlock(module, NULL, full,
                                            numDecisions + 1,
                                            BinaryenTypeNone());

  // locals: state, free-for-label
  BinaryenType localTypes[] = { BinaryenTypeInt32(), BinaryenTypeInt32() };
  BinaryenFunctionRef theMain = BinaryenAddFunction(
    module, "main", BinaryenTypeNone(), BinaryenTypeNone(), localTypes, 2, all);
  BinaryenSetStart(module, theMain);

  // import
  BinaryenAddFunctionImport(module,
                            "print",
                            "spectest",
                            "print",
                            BinaryenTypeInt32(),
                            BinaryenTypeNone());

  // memory
  BinaryenSetMemory(module, 1, 1, "mem", NULL, NULL, NULL, NULL, 0, 0);

  // optionally, optimize
  if (0) BinaryenModuleOptimize(module);

  assert(BinaryenModuleValidate(module));

  // write it out

  BinaryenModulePrint(module);

  BinaryenModuleDispose(module);

  return 0;
}
