require "spec"
require "../support/number"
require "big"

{% for i in Int::Signed.union_types %}
  struct {{i}}
    TEST_CASES = [MIN, MIN &+ 1, MIN &+ 2, -2, -1, 0, 1, 2, MAX &- 2, MAX &- 1, MAX] of {{i}}
  end
{% end %}

{% for i in Int::Unsigned.union_types %}
  struct {{i}}
    TEST_CASES = [MIN, MIN &+ 1, MIN &+ 2, MAX // 2 &- 1, MAX // 2, MAX // 2 &+ 1, MAX &- 2, MAX &- 1, MAX] of {{i}}
  end
{% end %}

macro run_op_tests(t, u, op)
  it "overflow test #{{{t}}} #{{{op}}} #{{{u}}}" do
    {{t}}::TEST_CASES.each do |lhs|
      {{u}}::TEST_CASES.each do |rhs|
        result = lhs.to_big_i {{op.id}} rhs.to_big_i
        passes = {{t}}::MIN <= result <= {{t}}::MAX
        begin
          if passes
            (lhs {{op.id}} rhs).should eq(lhs &{{op.id}} rhs)
          else
            expect_raises(OverflowError) { lhs {{op.id}} rhs }
          end
        rescue e : Spec::AssertionFailed
          raise Spec::AssertionFailed.new("#{e.message}: #{lhs} #{{{op}}} #{rhs}", e.file, e.line)
        rescue e : OverflowError
          raise OverflowError.new("#{e.message}: #{lhs} #{{{op}}} #{rhs}")
        end
      end
    end
  end
end

describe "Primitives: Int" do
  describe "#&+" do
    {% for int in BUILTIN_INTEGER_TYPES %}
      it "wraps around for {{ int }}" do
        ({{ int }}::MAX &+ {{ int }}.new(1)).should eq({{ int }}::MIN)
        ({{ int }}::MAX &+ 1_i64).should eq({{ int }}::MIN)
      end
    {% end %}
  end

  describe "#&-" do
    {% for int in BUILTIN_INTEGER_TYPES %}
      it "wraps around for {{ int }}" do
        ({{ int }}::MIN &- {{ int }}.new(1)).should eq({{ int }}::MAX)
        ({{ int }}::MIN &- 1_i64).should eq({{ int }}::MAX)
      end
    {% end %}
  end

  describe "#&*" do
    {% for int in BUILTIN_INTEGER_TYPES %}
      it "wraps around for {{ int }}" do
        %val{int} = {{ int }}::MAX // {{ int }}.new(2) &+ {{ int }}.new(1)
        (%val{int} &* {{ int }}.new(2)).should eq({{ int }}::MIN)
        (%val{int} &* 2_i64).should eq({{ int }}::MIN)
      end
    {% end %}
  end

  describe "#+" do
    {% for int1 in BUILTIN_INTEGER_TYPES %}
      {% for int2 in BUILTIN_INTEGER_TYPES %}
        run_op_tests {{ int1 }}, {{ int2 }}, :+
      {% end %}
    {% end %}
  end

  describe "#-" do
    {% for int1 in BUILTIN_INTEGER_TYPES %}
      {% for int2 in BUILTIN_INTEGER_TYPES %}
        run_op_tests {{ int1 }}, {{ int2 }}, :-
      {% end %}
    {% end %}
  end

  describe "#*" do
    {% for int1 in BUILTIN_INTEGER_TYPES %}
      {% for int2 in BUILTIN_INTEGER_TYPES %}
        run_op_tests {{ int1 }}, {{ int2 }}, :*
      {% end %}
    {% end %}
  end

  describe "#to_i" do
    {% for int1 in BUILTIN_INTEGER_TYPES %}
      {% for method, int2 in BUILTIN_INT_CONVERSIONS %}
        {% if int1 != int2 %}
          it {{ "raises on overflow for #{int1}##{method}" }} do
            if {{ int1 }}::MAX > {{ int2 }}::MAX
              expect_raises(OverflowError) do
                ({{ int1 }}.new!({{ int2 }}::MAX) &+ 1).{{ method }}
              end
            end

            if {{ int1 }}::MIN < {{ int2 }}::MIN
              expect_raises(OverflowError) do
                ({{ int1 }}.new!({{ int2 }}::MIN) &- 1).{{ method }}
              end
            end
          end
        {% end %}
      {% end %}
    {% end %}
  end

  describe "#to_i!" do
    it "works from negative values to unsigned types" do
      x = (-1).to_u!
      x.should be_a(UInt32)
      x.should eq(4294967295_u32)
    end

    it "works from greater values to smaller types" do
      x = 47866.to_i8!
      x.should be_a(Int8)
      x.should eq(-6_i8)
    end

    it "preserves negative sign" do
      x = (-1_i8).to_i!
      x.should be_a(Int32)
      x.should eq(-1_i32)
    end
  end

  describe "#unsafe_chr" do
    it "doesn't raise on overflow" do
      (0x41_i64 - 0x100000000_i64).unsafe_chr.should eq('A')
      0xFFFF_FFFF_0010_FFFF_u64.unsafe_chr.should eq('\u{10FFFF}')
    end
  end

  describe "#to_f" do
    it "raises on overflow for UInt128#to_f32" do
      expect_raises(OverflowError) { UInt128::MAX.to_f32 }
      expect_raises(OverflowError) { Float32::MAX.to_u128.succ.to_f32 } # Float32::MAX == 2 ** 128 - 2 ** 104
    end
  end

  describe "#to_f!" do
    it "doesn't raise on overflow for UInt128#to_f32" do
      UInt128::MAX.to_f32!.should eq(Float32::INFINITY)
      Float32::MAX.to_u128.succ.to_f32!.should eq(Float32::MAX)
    end
  end
end
