#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)),
                   "0"),
      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)),
                                "0"),
                   BinaryenConst(module, BinaryenLiteralInt32(4))),
    BinaryenTypeInt32(),
    "0");

  // 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)),
                              "0"),
                 "0"))};
  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)),
                              "0"),
                 "0");
  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
                  BinaryenTypeInt32(),
                  "0"));

  {
    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)),
                       "0"),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 6))),
        BinaryenTypeInt32(),
        "0"));
  }

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 5))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
                  BinaryenTypeInt32(),
                  "0"));

  {
    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)),
                       "0"),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
        BinaryenTypeInt32(),
        "0"));
  }

  {
    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)),
                       "0"),
          BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
        BinaryenTypeInt32(),
        "0"));
  }

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 3))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 1))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 4))),
                  BinaryenTypeInt32(),
                  "0"));

  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)),
                                 "0"),
                    BinaryenConst(module, BinaryenLiteralInt32(4 * 2))),
                  BinaryenTypeInt32(),
                  "0"));

  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(),
                      "0");
    }
  }
  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, NULL, 0, 0, 0, "0");

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

  assert(BinaryenModuleValidate(module));

  // write it out

  BinaryenModulePrint(module);

  BinaryenModuleDispose(module);

  return 0;
}
