# -*- encoding: binary -*-
require_relative 'spec_helper'

load_extension("integer")

describe "CApiIntegerSpecs" do
  before :each do
    @s = CApiIntegerSpecs.new
  end

  describe "rb_integer_pack" do
    it "converts zero" do
      words = "\000" * 9
      result = @s.rb_integer_pack(0, words, 1, 9, 0,
          CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
      result.should == 0
      words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00"
    end

    describe "without two's complement flag" do
      before :each do
        @value = 0x9876_abcd_4532_ef01_0123_4567_89ab_cdef
        @words = "\000" * 16
      end

      describe "with big endian output" do
        it "converts a positive number" do
          result = @s.rb_integer_pack(@value, @words, 2, 8, 0,
              CApiIntegerSpecs::BIG_ENDIAN)
          result.should == 1
          @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
        end

        it "converts a negative number" do
          result = @s.rb_integer_pack(-@value, @words, 2, 8, 0,
              CApiIntegerSpecs::BIG_ENDIAN)
          result.should == -1
          @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
        end

        it "converts a negative number exactly -2**(numwords*wordsize*8)" do
          result = @s.rb_integer_pack(-2**(2*8*8), @words, 2, 8, 0,
              CApiIntegerSpecs::BIG_ENDIAN)
          result.should == -2
          @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        end
      end

      describe "with little endian output" do
        it "converts a positive number" do
          result = @s.rb_integer_pack(@value, @words, 2, 8, 0,
              CApiIntegerSpecs::LITTLE_ENDIAN)
          result.should == 1
          @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98"
        end

        it "converts a negative number" do
          result = @s.rb_integer_pack(-@value, @words, 2, 8, 0,
              CApiIntegerSpecs::LITTLE_ENDIAN)
          result.should == -1
          @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98"
        end

        it "converts a negative number exactly -2**(numwords*wordsize*8)" do
          result = @s.rb_integer_pack(-2**(2*8*8), @words, 2, 8, 0,
              CApiIntegerSpecs::LITTLE_ENDIAN)
          result.should == -2
          @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
        end
      end
    end

    describe "with two's complement flag" do
      describe "with input less than 64 bits" do
        before :each do
          @value = 0x0123_4567_89ab_cdef
          @words = "\000" * 8
        end

        describe "with big endian output" do
          it "converts a positive number" do
            result = @s.rb_integer_pack(@value, @words, 1, 8, 0,
                CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == 1
            @words.should == "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
          end

          it "converts a negative number" do
            result = @s.rb_integer_pack(-@value, @words, 1, 8, 0,
                CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == -1
            @words.should == "\xFE\xDC\xBA\x98\x76\x54\x32\x11"
          end
        end

        describe "with little endian output" do
          it "converts a positive number" do
            result = @s.rb_integer_pack(@value, @words, 1, 8, 0,
                CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == 1
            @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01"
          end

          it "converts a negative number" do
            result = @s.rb_integer_pack(-@value, @words, 1, 8, 0,
                CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == -1
            @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE"
          end
        end

        describe "with native endian output" do
          big_endian do
            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 8, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\x01\x23\x45\x67\x89\xAB\xCD\xEF"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 8, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\xFE\xDC\xBA\x98\x76\x54\x32\x11"
            end
          end

          little_endian do
            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 8, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 8, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE"
            end

            it "converts numbers near the fixnum limit successfully" do
              result = @s.rb_integer_pack(0x7123_4567_89ab_cdef, @words, 1, 8, 0,
                CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x71"

              result = @s.rb_integer_pack(2**62-1, @words, 1, 8, 0,
                CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F"

              result = @s.rb_integer_pack(2**63-1, @words, 1, 8, 0,
                CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F"
            end
          end
        end
      end

      describe "with input greater than 64 bits" do
        before :each do
          @value = 0x9876_abcd_4532_ef01_0123_4567_89ab_cdef
          @words = "\000" * 16
        end

        describe "with big endian output" do
          it "converts a positive number" do
            result = @s.rb_integer_pack(@value, @words, 2, 8, 0,
                CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == 1
            @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
          end

          it "converts a negative number" do
            result = @s.rb_integer_pack(-@value, @words, 2, 8, 0,
                CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == -1
            @words.should == "\x67\x89\x54\x32\xBA\xCD\x10\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11"
          end

          describe "with overflow" do
            before :each do
              @words = "\000" * 9
            end

            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 9, 0,
                  CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == 2
              @words.should == "\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 9, 0,
                  CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == -2
              @words.should == "\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11"
            end

            it "converts a negative number exactly -2**(numwords*wordsize*8)" do
              result = @s.rb_integer_pack(-2**(9*8), @words, 1, 9, 0,
                  CApiIntegerSpecs::BIG_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            end
          end
        end

        describe "with little endian output" do
          it "converts a positive number" do
            result = @s.rb_integer_pack(@value, @words, 2, 8, 0,
                CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == 1
            @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98"
          end

          it "converts a negative number" do
            result = @s.rb_integer_pack(-@value, @words, 2, 8, 0,
                CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
            result.should == -1
            @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE\x10\xCD\xBA\x32\x54\x89\x67"
          end

          describe "with overflow" do
            before :each do
              @words = "\000" * 9
            end

            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 9, 0,
                  CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == 2
              @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 9, 0,
                  CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == -2
              @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE"
            end

            it "converts a negative number exactly -2**(numwords*wordsize*8)" do
              result = @s.rb_integer_pack(-2**(9*8), @words, 1, 9, 0,
                  CApiIntegerSpecs::LITTLE_ENDIAN|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\x00\x00\x00\x00\x00\x00\x00\x00\x00"
            end
          end
        end

        describe "with native endian output" do
          big_endian do
            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 16, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\x98\x76\xAB\xCD\x45\x32\xEF\x01\x01\x23\x45\x67\x89\xAB\xCD\xEF"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 16, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\x67\x89\x54\x32\xBA\xCD\x10\xFE\xFE\xDC\xBA\x98\x76\x54\x32\x11"
            end
          end

          little_endian do
            it "converts a positive number" do
              result = @s.rb_integer_pack(@value, @words, 1, 16, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == 1
              @words.should == "\xEF\xCD\xAB\x89\x67\x45\x23\x01\x01\xEF\x32\x45\xCD\xAB\x76\x98"
            end

            it "converts a negative number" do
              result = @s.rb_integer_pack(-@value, @words, 1, 16, 0,
                  CApiIntegerSpecs::NATIVE|CApiIntegerSpecs::PACK_2COMP)
              result.should == -1
              @words.should == "\x11\x32\x54\x76\x98\xBA\xDC\xFE\xFE\x10\xCD\xBA\x32\x54\x89\x67"
            end
          end
        end
      end
    end
  end

  describe "rb_int_positive_pow" do
    it "raises an integer to given power" do
      @s.rb_int_positive_pow(2, 3).should == 8
    end

    it "raises a negative integer to given power" do
      @s.rb_int_positive_pow(-2, 3).should == -8
      @s.rb_int_positive_pow(-2, 4).should == 16
    end

    it "overflows for large inputs" do
      @s.rb_int_positive_pow(8, 23).should == 590295810358705651712
    end
  end
end
