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, 5},
               {:section_body, [{[], [:i32]}]},
               {:section_id, 3},
               {:section_type, :function},
               {:section_length, 2},
               {:section_body, [0]},
               {:section_id, 5},
               {:section_type, :memory},
               {:section_length, 3},
               {:section_body, [{2, nil}]},
               {:section_id, 6},
               {:section_type, :global},
               {:section_length, 14},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_id, 7},
               {:section_type, :export},
               {:section_length, 48},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"return_0", {:func, 0}}
                ]},
               {:section_id, 10},
               {:section_type, :code},
               {:section_length, 6},
               {:section_body, [%{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]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"identity", {:func, 0}}
                ]},
               {:section_type, :code},
               {:section_body, [%{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, [0, 0, 0, 0]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"inc", {:func, 0}},
                  {"dec", {:func, 1}},
                  {"tri", {:func, 2}},
                  {"third", {:func, 3}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{
                    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]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"ge_select", {:func, 0}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{
                    code: %{
                      expr: [
                        {:"local.get", 1},
                        {:"local.get", 0},
                        {:"local.get", 0},
                        {:"local.get", 1},
                        :"i32.lt_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, [0, 0, 0, 0, 0, 0]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"eq", {:func, 0}},
                  {"ne", {:func, 1}},
                  {"lt", {:func, 2}},
                  {"gt", {:func, 3}},
                  {"le", {:func, 4}},
                  {"ge", {:func, 5}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{
                    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]},
               {:section_type, :memory},
               {:section_body, [{2, nil}]},
               {:section_type, :global},
               {:section_body,
                [{{:i32, :const}, ["i32.const": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"factorial", {:func, 0}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{
                    code: %{
                      expr: [
                        {:"i32.const", 1},
                        {:"local.set", 1},
                        {:"local.get", 0},
                        {:"i32.const", 1},
                        :"i32.ge_s",
                        {:if,
                         %{
                           blocktype: :empty,
                           instr: [
                             loop: %{
                               blocktype: :empty,
                               instr: [
                                 {:"local.get", 2},
                                 {:"i32.const", 1},
                                 :"i32.add",
                                 {:"local.tee", 2},
                                 {:"local.get", 1},
                                 :"i32.mul",
                                 {:"local.set", 1},
                                 {:"local.get", 0},
                                 {:"local.get", 2},
                                 :"i32.ne",
                                 {:br_if, 0}
                               ]
                             }
                           ]
                         }},
                        {:"local.get", 1}
                      ],
                      locals: [%{num: 2, type: :i32}]
                    },
                    size: 40
                  }
                ]}
             ]
  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, 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": 66560]}, {{:i32, :const}, ["i32.const": 1024]}]},
               {:section_type, :export},
               {:section_body,
                [
                  {"memory", {:mem, 0}},
                  {"__heap_base", {:global, 0}},
                  {"__data_end", {:global, 1}},
                  {"return_0", {:func, 0}},
                  {"identity", {:func, 1}},
                  {"inc", {:func, 2}},
                  {"dec", {:func, 3}},
                  {"tri", {:func, 4}},
                  {"third", {:func, 5}},
                  {"ge_select", {:func, 6}},
                  {"eq", {:func, 7}},
                  {"ne", {:func, 8}},
                  {"lt", {:func, 9}},
                  {"gt", {:func, 10}},
                  {"le", {:func, 11}},
                  {"ge", {:func, 12}},
                  {"factorial", {:func, 13}}
                ]},
               {:section_type, :code},
               {:section_body,
                [
                  %{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", 1},
                        {:"local.get", 0},
                        {:"local.get", 0},
                        {:"local.get", 1},
                        :"i32.lt_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},
                        {:"local.get", 0},
                        {:"i32.const", 1},
                        :"i32.ge_s",
                        {:if,
                         %{
                           blocktype: :empty,
                           instr: [
                             loop: %{
                               blocktype: :empty,
                               instr: [
                                 {:"local.get", 2},
                                 {:"i32.const", 1},
                                 :"i32.add",
                                 {:"local.tee", 2},
                                 {:"local.get", 1},
                                 :"i32.mul",
                                 {:"local.set", 1},
                                 {:"local.get", 0},
                                 {:"local.get", 2},
                                 :"i32.ne",
                                 {:br_if, 0}
                               ]
                             }
                           ]
                         }},
                        {:"local.get", 1}
                      ],
                      locals: [%{num: 2, type: :i32}]
                    },
                    size: 40
                  }
                ]}
             ]
  end
end