module ResolverSpecs
  def self.included(base)
    base.module_eval do
      let(:resolver) { nil }
      let(:result) { @result }

      before :each do
        # See the comment in the first before :each block in safe_yaml_spec.rb.
        require "safe_yaml"
      end

      def parse(yaml)
        tree = YAML.parse(yaml.unindent)
        @result = resolver.resolve_node(tree)
      end

      # Isn't this how I should've been doing it all along?
      def parse_and_test(yaml)
        safe_result = parse(yaml)

        exception_thrown = nil

        unsafe_result = begin
          YAML.unsafe_load(yaml)
        rescue Exception => e
          exception_thrown = e
        end

        if exception_thrown
          # If the underlying YAML parser (e.g. Psych) threw an exception, I'm
          # honestly not sure what the right thing to do is. For now I'll just
          # print a warning. Should SafeYAML fail when Psych fails?
          Kernel.warn "\n"
          Kernel.warn "Discrepancy between SafeYAML and #{SafeYAML::YAML_ENGINE} on input:\n"
          Kernel.warn "#{yaml.unindent}\n"
          Kernel.warn "SafeYAML result:"
          Kernel.warn "#{safe_result.inspect}\n"
          Kernel.warn "#{SafeYAML::YAML_ENGINE} result:"
          Kernel.warn "#{exception_thrown.inspect}\n"

        else
          expect(safe_result).to eq(unsafe_result)
        end
      end

      context "by default" do
        it "translates maps to hashes" do
          parse <<-YAML
            potayto: potahto
            tomayto: tomahto
          YAML

          expect(result).to eq({
            "potayto" => "potahto",
            "tomayto" => "tomahto"
          })
        end

        it "translates sequences to arrays" do
          parse <<-YAML
            - foo
            - bar
            - baz
          YAML

          expect(result).to eq(["foo", "bar", "baz"])
        end

        it "translates most values to strings" do
          parse "string: value"
          expect(result).to eq({ "string" => "value" })
        end

        it "does not deserialize symbols" do
          parse ":symbol: value"
          expect(result).to eq({ ":symbol" => "value" })
        end

        it "translates valid integral numbers to integers" do
          parse "integer: 1"
          expect(result).to eq({ "integer" => 1 })
        end

        it "translates valid decimal numbers to floats" do
          parse "float: 3.14"
          expect(result).to eq({ "float" => 3.14 })
        end

        it "translates valid dates" do
          parse "date: 2013-01-24"
          expect(result).to eq({ "date" => Date.parse("2013-01-24") })
        end

        it "translates valid true/false values to booleans" do
          parse <<-YAML
            - yes
            - true
            - no
            - false
          YAML

          expect(result).to eq([true, true, false, false])
        end

        it "translates valid nulls to nil" do
          parse <<-YAML
            - 
            - ~
            - null
          YAML

          expect(result).to eq([nil] * 3)
        end

        it "matches the behavior of the underlying YAML engine w/ respect to capitalization of boolean values" do
          parse_and_test <<-YAML
            - true
            - True
            - TRUE
            - tRue
            - TRue
            - False
            - FALSE
            - fAlse
            - FALse
          YAML

          # using Syck: [true, true, true, "tRue", "TRue", false, false, "fAlse", "FALse"]
          # using Psych: all booleans
        end

        it "matches the behavior of the underlying YAML engine w/ respect to capitalization of nil values" do
          parse_and_test <<-YAML
            - Null
            - NULL
            - nUll
            - NUll
          YAML

          # using Syck: [nil, nil, "nUll", "NUll"]
          # using Psych: all nils
        end

        it "translates quoted empty strings to strings (not nil)" do
          parse "foo: ''"
          expect(result).to eq({ "foo" => "" })
        end

        it "correctly reverse-translates strings encoded via #to_yaml" do
          parse "5.10".to_yaml
          expect(result).to eq("5.10")
        end

        it "does not specially parse any double-quoted strings" do
          parse <<-YAML
            - "1"
            - "3.14"
            - "true"
            - "false"
            - "2013-02-03"
            - "2013-02-03 16:27:00 -0600"
          YAML

          expect(result).to eq(["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"])
        end

        it "does not specially parse any single-quoted strings" do
          parse <<-YAML
            - '1'
            - '3.14'
            - 'true'
            - 'false'
            - '2013-02-03'
            - '2013-02-03 16:27:00 -0600'
          YAML

          expect(result).to eq(["1", "3.14", "true", "false", "2013-02-03", "2013-02-03 16:27:00 -0600"])
        end

        it "deals just fine with nested maps" do
          parse <<-YAML
            foo:
              bar:
                marco: polo
          YAML

          expect(result).to eq({ "foo" => { "bar" => { "marco" => "polo" } } })
        end

        it "deals just fine with nested sequences" do
          parse <<-YAML
            - foo
            -
              - bar1
              - bar2
              -
                - baz1
                - baz2
          YAML

          expect(result).to eq(["foo", ["bar1", "bar2", ["baz1", "baz2"]]])
        end

        it "applies the same transformations to keys as to values" do
          parse <<-YAML
            foo: string
            :bar: symbol
            1: integer
            3.14: float
            2013-01-24: date
          YAML

          expect(result).to eq({
            "foo"  => "string",
            ":bar" => "symbol",
            1      => "integer",
            3.14   => "float",
            Date.parse("2013-01-24") => "date",
          })
        end

        it "applies the same transformations to elements in sequences as to all values" do
          parse <<-YAML
            - foo
            - :bar
            - 1
            - 3.14
            - 2013-01-24
          YAML

          expect(result).to eq(["foo", ":bar", 1, 3.14, Date.parse("2013-01-24")])
        end
      end

      context "for Ruby version #{RUBY_VERSION}" do
        it "translates valid time values" do
          parse "time: 2013-01-29 05:58:00 -0800"
          expect(result).to eq({ "time" => Time.utc(2013, 1, 29, 13, 58, 0) })
        end

        it "applies the same transformation to elements in sequences" do
          parse "- 2013-01-29 05:58:00 -0800"
          expect(result).to eq([Time.utc(2013, 1, 29, 13, 58, 0)])
        end

        it "applies the same transformation to keys" do
          parse "2013-01-29 05:58:00 -0800: time"
          expect(result).to eq({ Time.utc(2013, 1, 29, 13, 58, 0) => "time" })
        end
      end

      context "with symbol deserialization enabled" do
        before :each do
          SafeYAML::OPTIONS[:deserialize_symbols] = true
        end

        after :each do
          SafeYAML.restore_defaults!
        end

        it "translates values starting with ':' to symbols" do
          parse "symbol: :value"
          expect(result).to eq({ "symbol" => :value })
        end

        it "applies the same transformation to keys" do
          parse ":bar: symbol"
          expect(result).to eq({ :bar  => "symbol" })
        end

        it "applies the same transformation to elements in sequences" do
          parse "- :bar"
          expect(result).to eq([:bar])
        end
      end
    end
  end
end