defmodule WaParserTest do
  use ExUnit.Case

  def parse_file(filename) do
    filename
    |> File.read!()
    |> WaParser.stream()
    |> Enum.to_list()
  end

  test "0_return_0" do
    assert parse_file("test_data/0_return_0.opt.wasm") ==
             [
               {:magic, <<0, 97, 115, 109>>},
               {:version, <<1, 0, 0, 0>>},
               {:section_id, 1},
               {:section_type, :type},
               {:section_length, 8},
               {:section_body, [{[], []}, {[], [:i32]}]},
               {:section_id, 3},
               {:section_type, :function},
               {:section_length, 3},
               {:section_body, [0, 1]},
               {:section_id, 6},
               {:section_type, :global},
               {:section_length, 56},
               {:section_body,
                [
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 131_072]},
                  {{:i32, :const}, ["i32.const": 0]},
                  {{:i32, :const}, ["i32.const": 1]}
                ]},
               {:section_id, 7},
               {:section_type, :export},
               {:section_length, 163},
               {:section_body,
                [
                  {"__wasm_call_ctors", {:func, 0}},
                  {"return_0", {:func, 1}},
                  {"__dso_handle", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"__stack_low", {:global, 2}},
                  {"__stack_high", {:global, 3}},
                  {"__global_base", {:global, 4}},
                  {"__heap_base", {:global, 5}},
                  {"__heap_end", {:global, 6}},
                  {"__memory_base", {:global, 7}},
                  {"__table_base", {:global, 8}}
                ]},
               {:section_id, 10},
               {:section_type, :code},
               {:section_length, 9},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{code: %{expr: ["i32.const": 0], locals: []}, size: 4}
                ]}
             ]
  end

  test "1_int_identity" do
    assert parse_file("test_data/1_int_identity.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body, [{[], []}, {[:i32], [:i32]}]},
               {:section_type, :function},
               {:section_body, [0, 1]},
               {:section_type, :global},
               {:section_body,
                [
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 131_072]},
                  {{:i32, :const}, ["i32.const": 0]},
                  {{:i32, :const}, ["i32.const": 1]}
                ]},
               {:section_type, :export},
               {:section_body,
                [
                  {"__wasm_call_ctors", {:func, 0}},
                  {"identity", {:func, 1}},
                  {"__dso_handle", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"__stack_low", {:global, 2}},
                  {"__stack_high", {:global, 3}},
                  {"__global_base", {:global, 4}},
                  {"__heap_base", {:global, 5}},
                  {"__heap_end", {:global, 6}},
                  {"__memory_base", {:global, 7}},
                  {"__table_base", {:global, 8}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{code: %{expr: ["local.get": 0], locals: []}, size: 4}
                ]}
             ]
  end

  test "2_int_arithm" do
    assert parse_file("test_data/2_int_arithm.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body, [{[:i32], [:i32]}, {[], []}]},
               {:section_type, :function},
               {:section_body, [1, 0, 0, 0, 0]},
               {:section_type, :global},
               {
                 :section_body,
                 [
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 131_072]},
                   {{:i32, :const}, ["i32.const": 0]},
                   {{:i32, :const}, ["i32.const": 1]}
                 ]
               },
               {:section_type, :export},
               {
                 :section_body,
                 [
                   {"__wasm_call_ctors", {:func, 0}},
                   {"inc", {:func, 1}},
                   {"dec", {:func, 2}},
                   {"tri", {:func, 3}},
                   {"third", {:func, 4}},
                   {"__dso_handle", {:global, 0}},
                   {"__data_end", {:global, 1}},
                   {"__stack_low", {:global, 2}},
                   {"__stack_high", {:global, 3}},
                   {"__global_base", {:global, 4}},
                   {"__heap_base", {:global, 5}},
                   {"__heap_end", {:global, 6}},
                   {"__memory_base", {:global, 7}},
                   {"__table_base", {:global, 8}}
                 ]
               },
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"i32.const", 1}, :"i32.add"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"i32.const", 1}, :"i32.sub"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"i32.const", 3}, :"i32.mul"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [{:"local.get", 0}, {:"i32.const", 3}, :"i32.div_s"],
                      locals: []
                    },
                    size: 7
                  }
                ]}
             ]
  end

  test "3_int_cond" do
    assert parse_file("test_data/3_int_cond.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body, [{[], []}, {[:i32, :i32], [:i32]}]},
               {:section_type, :function},
               {:section_body, [0, 1]},
               {:section_type, :global},
               {
                 :section_body,
                 [
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 131_072]},
                   {{:i32, :const}, ["i32.const": 0]},
                   {{:i32, :const}, ["i32.const": 1]}
                 ]
               },
               {:section_type, :export},
               {
                 :section_body,
                 [
                   {"__wasm_call_ctors", {:func, 0}},
                   {"ge_select", {:func, 1}},
                   {"__dso_handle", {:global, 0}},
                   {"__data_end", {:global, 1}},
                   {"__stack_low", {:global, 2}},
                   {"__stack_high", {:global, 3}},
                   {"__global_base", {:global, 4}},
                   {"__heap_base", {:global, 5}},
                   {"__heap_end", {:global, 6}},
                   {"__memory_base", {:global, 7}},
                   {"__table_base", {:global, 8}}
                 ]
               },
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"local.get", 1},
                        {:"local.get", 0},
                        {:"local.get", 1},
                        :"i32.gt_s",
                        :select
                      ],
                      locals: []
                    },
                    size: 12
                  }
                ]}
             ]
  end

  test "4_int_comp" do
    assert parse_file("test_data/4_int_comp.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body, [{[:i32, :i32], [:i32]}, {[], []}]},
               {:section_type, :function},
               {:section_body, [1, 0, 0, 0, 0, 0, 0]},
               {:section_type, :global},
               {
                 :section_body,
                 [
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 131_072]},
                   {{:i32, :const}, ["i32.const": 0]},
                   {{:i32, :const}, ["i32.const": 1]}
                 ]
               },
               {:section_type, :export},
               {
                 :section_body,
                 [
                   {"__wasm_call_ctors", {:func, 0}},
                   {"eq", {:func, 1}},
                   {"ne", {:func, 2}},
                   {"lt", {:func, 3}},
                   {"gt", {:func, 4}},
                   {"le", {:func, 5}},
                   {"ge", {:func, 6}},
                   {"__dso_handle", {:global, 0}},
                   {"__data_end", {:global, 1}},
                   {"__stack_low", {:global, 2}},
                   {"__stack_high", {:global, 3}},
                   {"__global_base", {:global, 4}},
                   {"__heap_base", {:global, 5}},
                   {"__heap_end", {:global, 6}},
                   {"__memory_base", {:global, 7}},
                   {"__table_base", {:global, 8}}
                 ]
               },
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.eq"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.ne"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.lt_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.gt_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.le_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.ge_s"], locals: []},
                    size: 7
                  }
                ]}
             ]
  end

  test "5_int_factorial" do
    assert parse_file("test_data/5_int_factorial.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body, [{[], []}, {[:i32], [:i32]}]},
               {:section_type, :function},
               {:section_body, [0, 1]},
               {:section_type, :global},
               {
                 :section_body,
                 [
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 1024]},
                   {{:i32, :const}, ["i32.const": 66560]},
                   {{:i32, :const}, ["i32.const": 131_072]},
                   {{:i32, :const}, ["i32.const": 0]},
                   {{:i32, :const}, ["i32.const": 1]}
                 ]
               },
               {:section_type, :export},
               {
                 :section_body,
                 [
                   {"__wasm_call_ctors", {:func, 0}},
                   {"factorial", {:func, 1}},
                   {"__dso_handle", {:global, 0}},
                   {"__data_end", {:global, 1}},
                   {"__stack_low", {:global, 2}},
                   {"__stack_high", {:global, 3}},
                   {"__global_base", {:global, 4}},
                   {"__heap_base", {:global, 5}},
                   {"__heap_end", {:global, 6}},
                   {"__memory_base", {:global, 7}},
                   {"__table_base", {:global, 8}}
                 ]
               },
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{
                    code: %{
                      expr: [
                        "i32.const": 1,
                        "local.set": 1,
                        loop: %{
                          instr: [
                            {:"local.get", 0},
                            {:"i32.const", 0},
                            :"i32.le_s",
                            {:br_if, 0},
                            {:"local.get", 0},
                            {:"i32.const", 7},
                            :"i32.and",
                            {:"local.set", 2},
                            {:loop,
                             %{
                               instr: [
                                 {:"local.get", 0},
                                 {:"i32.const", 8},
                                 :"i32.lt_u",
                                 {:if, %{instr: ["i32.const": 1, br: 1], blocktype: :empty}},
                                 {:"i32.const", 0},
                                 {:"local.get", 0},
                                 {:"i32.const", 2_147_483_640},
                                 :"i32.and",
                                 :"i32.sub",
                                 {:"local.set", 3},
                                 {:"i32.const", 8},
                                 {:"local.set", 0},
                                 {:loop,
                                  %{
                                    instr: [
                                      {:"local.get", 0},
                                      {:"local.get", 0},
                                      {:"i32.const", 1},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 2},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 3},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 4},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 5},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 6},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 7},
                                      :"i32.sub",
                                      {:"local.get", 1},
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      {:"local.set", 1},
                                      {:"local.get", 3},
                                      {:"local.get", 0},
                                      {:"i32.const", 8},
                                      :"i32.add",
                                      {:"local.tee", 0},
                                      :"i32.add",
                                      {:"i32.const", 8},
                                      :"i32.ne",
                                      {:br_if, 0}
                                    ],
                                    blocktype: :empty
                                  }},
                                 {:"local.get", 0},
                                 {:"i32.const", 7},
                                 :"i32.sub"
                               ],
                               blocktype: :i32
                             }},
                            {:"local.set", 0},
                            {:"local.get", 2},
                            :"i32.eqz",
                            {:br_if, 0},
                            {:loop,
                             %{
                               instr: [
                                 {:"local.get", 0},
                                 {:"local.get", 1},
                                 :"i32.mul",
                                 {:"local.set", 1},
                                 {:"local.get", 0},
                                 {:"i32.const", 1},
                                 :"i32.add",
                                 {:"local.set", 0},
                                 {:"local.get", 2},
                                 {:"i32.const", 1},
                                 :"i32.sub",
                                 {:"local.tee", 2},
                                 {:br_if, 0}
                               ],
                               blocktype: :empty
                             }}
                          ],
                          blocktype: :empty
                        },
                        "local.get": 1
                      ],
                      locals: [%{type: :i32, num: 3}]
                    },
                    size: 165
                  }
                ]}
             ]
  end

  test "9_all" do
    assert parse_file("test_data/9_all.opt.wasm")
           |> Enum.filter(fn {k, _} -> k == :section_type or k == :section_body end) ==
             [
               {:section_type, :type},
               {:section_body,
                [{[:i32, :i32], [:i32]}, {[:i32], [:i32]}, {[], []}, {[], [:i32]}]},
               {:section_type, :function},
               {:section_body, [2, 3, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 1]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 1024]},
                  {{:i32, :const}, ["i32.const": 66560]},
                  {{:i32, :const}, ["i32.const": 131_072]},
                  {{:i32, :const}, ["i32.const": 0]},
                  {{:i32, :const}, ["i32.const": 1]}
                ]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__wasm_call_ctors", {:func, 0}},
                  {"return_0", {:func, 1}},
                  {"identity", {:func, 2}},
                  {"inc", {:func, 3}},
                  {"dec", {:func, 4}},
                  {"tri", {:func, 5}},
                  {"third", {:func, 6}},
                  {"ge_select", {:func, 7}},
                  {"eq", {:func, 8}},
                  {"ne", {:func, 9}},
                  {"lt", {:func, 10}},
                  {"gt", {:func, 11}},
                  {"le", {:func, 12}},
                  {"ge", {:func, 13}},
                  {"factorial", {:func, 14}},
                  {"__dso_handle", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"__stack_low", {:global, 2}},
                  {"__stack_high", {:global, 3}},
                  {"__global_base", {:global, 4}},
                  {"__heap_base", {:global, 5}},
                  {"__heap_end", {:global, 6}},
                  {"__memory_base", {:global, 7}},
                  {"__table_base", {:global, 8}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{code: %{expr: [], locals: []}, size: 2},
                  %{code: %{expr: ["i32.const": 0], locals: []}, size: 4},
                  %{code: %{expr: ["local.get": 0], locals: []}, size: 4},
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"i32.const", 1},
                        :"i32.add"
                      ],
                      locals: []
                    },
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"i32.const", 1},
                        :"i32.sub"
                      ],
                      locals: []
                    },
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"i32.const", 3},
                        :"i32.mul"
                      ],
                      locals: []
                    },
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"i32.const", 3},
                        :"i32.div_s"
                      ],
                      locals: []
                    },
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 0},
                        {:"local.get", 1},
                        {:"local.get", 0},
                        {:"local.get", 1},
                        :"i32.gt_s",
                        :select
                      ],
                      locals: []
                    },
                    size: 12
                  },
                  %{
                    code: %{
                      expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.eq"],
                      locals: []
                    },
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.ne"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.lt_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.gt_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.le_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{expr: [{:"local.get", 0}, {:"local.get", 1}, :"i32.ge_s"], locals: []},
                    size: 7
                  },
                  %{
                    code: %{
                      expr: [
                        "i32.const": 1,
                        "local.set": 1,
                        loop: %{
                          instr: [
                            {:"local.get", 0},
                            {:"i32.const", 0},
                            :"i32.le_s",
                            {:br_if, 0},
                            {:"local.get", 0},
                            {:"i32.const", 7},
                            :"i32.and",
                            {:"local.set", 2},
                            {:loop,
                             %{
                               instr: [
                                 {:"local.get", 0},
                                 {:"i32.const", 8},
                                 :"i32.lt_u",
                                 {:if, %{instr: ["i32.const": 1, br: 1], blocktype: :empty}},
                                 {:"i32.const", 0},
                                 {:"local.get", 0},
                                 {:"i32.const", 2_147_483_640},
                                 :"i32.and",
                                 :"i32.sub",
                                 {:"local.set", 3},
                                 {:"i32.const", 8},
                                 {:"local.set", 0},
                                 {:loop,
                                  %{
                                    instr: [
                                      {:"local.get", 0},
                                      {:"local.get", 0},
                                      {:"i32.const", 1},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 2},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 3},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 4},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 5},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 6},
                                      :"i32.sub",
                                      {:"local.get", 0},
                                      {:"i32.const", 7},
                                      :"i32.sub",
                                      {:"local.get", 1},
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      :"i32.mul",
                                      {:"local.set", 1},
                                      {:"local.get", 3},
                                      {:"local.get", 0},
                                      {:"i32.const", 8},
                                      :"i32.add",
                                      {:"local.tee", 0},
                                      :"i32.add",
                                      {:"i32.const", 8},
                                      :"i32.ne",
                                      {:br_if, 0}
                                    ],
                                    blocktype: :empty
                                  }},
                                 {:"local.get", 0},
                                 {:"i32.const", 7},
                                 :"i32.sub"
                               ],
                               blocktype: :i32
                             }},
                            {:"local.set", 0},
                            {:"local.get", 2},
                            :"i32.eqz",
                            {:br_if, 0},
                            {:loop,
                             %{
                               instr: [
                                 {:"local.get", 0},
                                 {:"local.get", 1},
                                 :"i32.mul",
                                 {:"local.set", 1},
                                 {:"local.get", 0},
                                 {:"i32.const", 1},
                                 :"i32.add",
                                 {:"local.set", 0},
                                 {:"local.get", 2},
                                 {:"i32.const", 1},
                                 :"i32.sub",
                                 {:"local.tee", 2},
                                 {:br_if, 0}
                               ],
                               blocktype: :empty
                             }}
                          ],
                          blocktype: :empty
                        },
                        "local.get": 1
                      ],
                      locals: [%{num: 3, type: :i32}]
                    },
                    size: 165
                  }
                ]}
             ]
  end
end