RSpec.describe H3 do
  include_context "constants"

  describe ".k_ring" do
    let(:h3_index) { "8928308280fffff".to_i(16) }

    subject(:k_ring) { H3.k_ring(h3_index, k) }

    context "when k range is 1" do
      let(:k) { 1 }
      let(:count) { 7 }
      let(:expected) do
        %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff
           8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) }
      end

      it "has 7 hexagons" do
        expect(k_ring.count).to eq count
      end

      it "has the expected hexagons" do
        expect(k_ring).to eq expected
      end
    end

    context "when k range is 2" do
      let(:k) { 2 }
      let(:count) { 19 }

      it "has 19 hexagons" do
        expect(k_ring.count).to eq count
      end
    end

    context "when k range is 10" do
      let(:k) { 10 }
      let(:count) { 331 }

      it "has 331 hexagons" do
        expect(k_ring.count).to eq count
      end
    end
  end

  describe ".max_kring_size" do
    let(:k) { 2 }
    let(:result) { 19 }

    subject(:max_kring_size) { H3.max_kring_size(k) }

    it { is_expected.to eq(result) }

    context "when provided with a bad k value" do
      let(:k) { "boom" }

      it "raises an error" do
        expect { max_kring_size }.to raise_error(TypeError)
      end
    end

    context "when given a k too large" do
      let(:k) { too_long_number }

      it "raises an error" do
        expect { max_kring_size }.to raise_error(RangeError)
      end
    end  
  end

  describe ".k_ring_distances" do
    let(:h3_index) { "8928308280fffff".to_i(16) }
    let(:k) { 1 }
    let(:outer_ring) do
      [
        "8928308280bffff", "89283082873ffff", "89283082877ffff",
        "8928308283bffff", "89283082807ffff", "89283082803ffff"
      ].map { |i| i.to_i(16) }
    end

    subject(:k_ring_distances) { H3.k_ring_distances(h3_index, k) }

    it "has two ring sets" do
      expect(k_ring_distances.count).to eq 2
    end

    it "has an inner ring containing hexagons of distance 0" do
      expect(k_ring_distances[0]).to eq [h3_index]
    end

    it "has an outer ring containing hexagons of distance 1" do
      expect(k_ring_distances[1].count).to eq 6
    end

    it "has an outer ring containing all expected indexes" do
      k_ring_distances[1].each do |index|
        expect(outer_ring).to include(index)
      end
    end
  end

  describe ".hex_range" do
    let(:h3_index) { "8928308280fffff".to_i(16) }

    subject(:hex_range) { H3.hex_range(h3_index, k) }

    context "when k range is 1" do
      let(:k) { 1 }
      let(:count) { 7 }
      let(:expected) do
        %w(8928308280fffff 8928308280bffff 89283082873ffff 89283082877ffff
           8928308283bffff 89283082807ffff 89283082803ffff).map { |i| i.to_i(16) }
      end

      it "has 7 hexagons" do
        expect(hex_range.count).to eq count
      end

      it "has the expected hexagons" do
        expect(hex_range).to eq expected
      end
    end

    context "when k range is 2" do
      let(:k) { 2 }
      let(:count) { 19 }

      it "has 19 hexagons" do
        expect(hex_range.count).to eq count
      end
    end

    context "when k range is 10" do
      let(:k) { 10 }
      let(:count) { 331 }

      it "has 331 hexagons" do
        expect(hex_range.count).to eq count
      end
    end

    context "when range contains a pentagon" do
      let(:h3_index) { "821c07fffffffff".to_i(16) }
      let(:k) { 1 }

      it "raises an error" do
        expect { hex_range }.to raise_error(ArgumentError)
      end
    end
  end

  describe ".hex_range_distances" do
    let(:h3_index) { "85283473fffffff".to_i(16) }
    let(:k) { 1 }
    let(:outer_ring) do
      [
        "85283447fffffff", "8528347bfffffff", "85283463fffffff",
        "85283477fffffff", "8528340ffffffff", "8528340bfffffff"
      ].map { |i| i.to_i(16) }
    end

    subject(:hex_range_distances) { H3.hex_range_distances(h3_index, k) }

    it "has two range sets" do
      expect(hex_range_distances.count).to eq 2
    end

    it "has an inner range containing hexagons of distance 0" do
      expect(hex_range_distances[0]).to eq [h3_index]
    end

    it "has an outer range containing hexagons of distance 1" do
      expect(hex_range_distances[1].count).to eq 6
    end

    it "has an outer range containing all expected indexes" do
      hex_range_distances[1].each do |index|
        expect(outer_ring).to include(index)
      end
    end

    context "when there is pentagonal distortion" do
      let(:h3_index) { "821c07fffffffff".to_i(16) }

      it "raises an error" do
        expect { hex_range_distances }.to raise_error(ArgumentError)
      end
    end
  end

  describe ".hex_ranges" do
    let(:h3_index) { "8928308280fffff".to_i(16) }
    let(:h3_set) { [h3_index] }
    let(:k) { 1 }
    let(:outer_ring) do
      [
        "8928308280bffff", "89283082807ffff", "89283082877ffff",
        "89283082803ffff", "89283082873ffff", "8928308283bffff"
      ].map { |i| i.to_i(16) }
    end

    subject(:hex_ranges) { H3.hex_ranges(h3_set, k) }

    it "contains a single k/v pair" do
      expect(hex_ranges.count).to eq 1
    end

    it "has one key, the h3_index" do
      expect(hex_ranges.keys.first).to eq h3_index
    end

    it "has two ring sets" do
      expect(hex_ranges[h3_index].count).to eq 2
    end

    it "has an inner ring containing only the original index" do
      expect(hex_ranges[h3_index].first).to eq [h3_index]
    end

    it "has an outer ring containing six indexes" do
      expect(hex_ranges[h3_index].last.count).to eq 6
    end

    it "has an outer ring containing all expected indexes" do
      hex_ranges[h3_index].last.each do |index|
        expect(outer_ring).to include(index)
      end
    end

    context "when there is pentagonal distortion" do
      let(:h3_index) { "821c07fffffffff".to_i(16) }

      it "raises an error" do
        expect { hex_ranges }.to raise_error(ArgumentError)
      end
    end

    context "when k is 2" do
      let(:k) { 2 }

      it "contains 3 rings" do
        expect(hex_ranges[h3_index].count).to eq 3
      end

      it "has an inner ring of size 1" do
        expect(hex_ranges[h3_index][0].count).to eq 1
      end

      it "has a middle ring of size 6" do
        expect(hex_ranges[h3_index][1].count).to eq 6
      end

      it "has an outer ring of size 12" do
        expect(hex_ranges[h3_index][2].count).to eq 12
      end
    end

    context "when run without grouping" do
      let(:hex_array) do
        [
          617700169958293503, 617700169958031359, 617700169964847103,
          617700169965109247, 617700169961177087, 617700169957769215,
          617700169957507071
        ]
      end

      subject(:hex_ranges) { H3.hex_ranges(h3_set, k, grouped: false) }

      it { is_expected.to eq hex_array }
    end

    context "when compared with the ungrouped version" do
      let(:h3_index2) { "8f19425b6ccd582".to_i(16) }
      let(:h3_index3) { "89283082873ffff".to_i(16) }
      let(:h3_set) { [h3_index, h3_index2, h3_index3]}
      let(:ungrouped) { H3.hex_ranges(h3_set, k, grouped: false) }
      let(:k) { 3 }

      it "has the same elements when we remove grouping" do
        expect(hex_ranges.values.flatten).to eq(ungrouped)
      end
    end
  end

  describe ".hex_ring" do
    let(:h3_index) { "8928308280fffff".to_i(16) }

    subject(:hex_ring) { H3.hex_ring(h3_index, k) }

    context "when k range is 1" do
      let(:k) { 1 }
      let(:count) { 6 }
      let(:expected) do
        %w(89283082803ffff 8928308280bffff 89283082873ffff 89283082877ffff
           8928308283bffff 89283082807ffff).map { |i| i.to_i(16) }
      end

      it "has 6 hexagons" do
        expect(hex_ring.count).to eq count
      end

      it "has the expected hexagons" do
        expect(hex_ring).to eq expected
      end
    end

    context "when k range is 2" do
      let(:k) { 2 }
      let(:count) { 12 }

      it "has 12 hexagons" do
        expect(hex_ring.count).to eq count
      end
    end

    context "when k range is 10" do
      let(:k) { 10 }
      let(:count) { 60 }

      it "has 60 hexagons" do
        expect(hex_ring.count).to eq count
      end
    end

    context "when the ring contains a pentagon" do
      let(:h3_index) { "821c07fffffffff".to_i(16) }
      let(:k) { 1 }

      it "raises an error" do
        expect { hex_ring }.to raise_error(ArgumentError)
      end
    end
  end

  describe ".distance" do
    let(:origin) { "89283082993ffff".to_i(16) }
    let(:destination) { "89283082827ffff".to_i(16) }
    let(:result) { 5 }

    subject(:distance) { H3.distance(origin, destination) }

    it { is_expected.to eq(result) }
  end

  describe ".line_size" do
    let(:origin) { "89283082993ffff".to_i(16) }
    let(:destination) { "89283082827ffff".to_i(16) }
    let(:result) { 6 }

    subject(:h3_line_size) { H3.line_size(origin, destination) }

    it { is_expected.to eq(result) }
  end

  describe ".line" do
    let(:origin) { "89283082993ffff".to_i(16) }
    let(:destination) { "89283082827ffff".to_i(16) }
    let(:result) do
      [
        "89283082993ffff", "8928308299bffff", "892830829d7ffff",
        "892830829c3ffff", "892830829cbffff", "89283082827ffff"
      ].map { |i| i.to_i(16) }
    end

    subject(:line) { H3.line(origin, destination) }

    it { is_expected.to eq(result) }
  end
end