# rubocop:todo all
require 'spec_helper'

describe Regexp::Raw do

  let(:pattern) { '\W+' }
  let(:options) { '' }
  let(:bson) { "#{pattern}#{BSON::NULL_BYTE}#{options}#{BSON::NULL_BYTE}" }

  describe "#as_json" do

    let(:object) do
      described_class.new(pattern, 'im')
    end

    it "returns the legacy serialization including regex pattern and options" do
      expect(object.as_json).to eq({ "$regex" => "\\W+", "$options" => "im" })
    end

    it_behaves_like "a JSON serializable object"
  end

  describe '#as_extended_json' do

    let(:object) do
      described_class.new(pattern, 'im')
    end

    context 'legacy mode' do
      it "returns the legacy serialization including regex pattern and options" do
        expect(object.as_extended_json(mode: :legacy)).to eq({ "$regex" => "\\W+", "$options" => "im" })
      end
    end

    context 'canonical/relaxed mode' do
      it "returns the extended json 2.0 serialization" do
        expect(object.as_extended_json).to eq(
          "$regularExpression" => {'pattern' => "\\W+", "options" => "im"}
        )
      end
    end
  end

  describe "#to_bson/#from_bson" do

    let(:options) { 'ilmsux' }
    let(:obj)  { described_class.new(pattern, options) }
    let(:type) { 11.chr }
    let(:bson) { "#{pattern}#{BSON::NULL_BYTE}#{options}#{BSON::NULL_BYTE}" }

    let(:klass) { ::Regexp }

    it_behaves_like "a bson element"
    it_behaves_like "a serializable bson element"
    it_behaves_like "a deserializable bson element"
  end

  describe "#initialize" do

    let(:object) do
      described_class.new(pattern, options)
    end

    context "when options are not passed" do

      it "sets the options on the raw regex" do
        expect(object.options). to eq(options)
      end

      context "When the raw regexp is compiled" do

        let(:regexp) do
          object.compile
        end

        it "sets the options on the compiled regexp object" do
          expect(regexp.options).to eq(0)
        end
      end
    end

    context "when options are passed" do

      context "when options are an Integer" do

        let(:options) { ::Regexp::EXTENDED }

        it "raises an error" do
          expect do
            object
          end.to raise_error(ArgumentError, /Regexp options must be a String or Symbol/)
        end
      end

      context "when options are a String" do

        let(:options) { 'x' }

        it "sets the options on the raw regex" do
          expect(object.options). to eq(options)
        end

        context "When the raw regexp is compiled" do

          let(:regexp) do
            object.compile
          end

          it "sets the options on the compiled regexp object" do
            expect(regexp.options).to eq(::Regexp::EXTENDED)
          end
        end
      end
    end

    context 'when options are not passed' do

      let(:object) do
        described_class.new(pattern)
      end

      it "sets no options on the raw regex" do
        expect(object.options). to eq('')
      end

      context "When the raw regexp is compiled" do

        let(:regexp) do
          object.compile
        end

        it "sets the options on the compiled regexp object" do
          expect(regexp.options).to eq(0)
        end
      end
    end
  end

  describe "#from_bson" do

    let(:obj) { ::Regexp.from_bson(io) }
    let(:io) { BSON::ByteBuffer.new(bson) }

    it "deserializes to a Regexp::Raw object" do
      expect(obj).to be_a(Regexp::Raw)
    end

    it "deserializes the pattern" do
      expect(obj.pattern).to eq(pattern)
    end

    context "when there are no options" do

      it "does not set any options on the raw regexp object" do
        expect(obj.options).to eq(options)
      end
    end

    context "when there are options" do

      context "when there is the i ignorecase option" do

        let(:options) { 'i' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the i option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when there is the l locale dependent option" do

        let(:options) { 'l' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the l option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when there is the m multiline option" do

        let(:options) { 'm' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the m option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when there is the s dotall option" do

        let(:options) { 's' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the s option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when there is the u match unicode option" do

        let(:options) { 'u' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the u option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when there is the x verbose option" do

        let(:options) { 'x' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets the x option on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end

      context "when all options are set" do

        let(:options) { 'ilmsux' }

        it "deserializes the pattern" do
          expect(obj.pattern).to eq(pattern)
        end

        it "sets all options on the raw regexp object" do
          expect(obj.options).to eq(options)
        end
      end
    end
  end

  context "when a method is called on a Raw regexp object" do

    let(:obj) { ::Regexp.from_bson(io) }
    let(:io) { BSON::ByteBuffer.new(bson) }

    it "forwards the method call on to the compiled Ruby Regexp object" do
      expect(obj.source).to eq(pattern)
    end
  end

  context "when respond_to? is called on the Raw Regexp object" do

    let(:obj) { Regexp::Raw.new(pattern, options) }

    context "when include_private is false" do

      it "does not consider private methods" do
        expect(obj.respond_to?(:initialize_copy)).to eq(false)
      end
    end

    context "when include private is true" do

      it "considers private methods" do
        expect(obj.respond_to?(:initialize_copy, true)).to eq(true)
      end
    end

    context "when include_private is not specified" do

      it "does not consider private methods" do
        expect(obj.respond_to?(:initialize_copy)).to eq(false)
      end
    end
  end

  context "#to_bson" do

    let(:obj) { Regexp::Raw.new(pattern, options) }
    let(:options) { '' }
    let(:bson) { "#{pattern}#{BSON::NULL_BYTE}#{options}#{BSON::NULL_BYTE}" }
    let(:serialized) { obj.to_bson.to_s }

    it "serializes the pattern" do
      expect(serialized).to eq(bson)
    end

    context "where there are no options" do

      it "does not set any options on the bson regex object" do
        expect(serialized).to eq(bson)
      end
    end

    context "when there are options" do

      context "when options are specified as an Integer" do

        let(:options) { ::Regexp::EXTENDED }
        let(:bson) { "#{pattern}#{BSON::NULL_BYTE}mx#{BSON::NULL_BYTE}" }

        it "raises an error" do
          expect do
            serialized
          end.to raise_error(ArgumentError, /Regexp options must be a String or Symbol/)
        end
      end

      context "when there is the i ignorecase option" do

        let(:options) { 'i' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when there is the l locale dependent option" do

        let(:options) { 'l' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when there is the m multiline option" do

        let(:options) { 'm' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when there is the s dotall option" do

        let(:options) { 's' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when there is the u match unicode option" do

        let(:options) { 'u' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when there is the x verbose option" do

        let(:options) { 'x' }

        it "sets the option on the serialized bson object" do
          expect(serialized).to eq(bson)
        end
      end

      context "when all options are set" do

        let(:options) { 'ilmsux' }

        it "sets all options on the serialized bson object" do
          expect(serialized).to eq(bson)
        end

        context "when the options are not provided in alphabetical order" do

          let(:options) { 'mislxu' }
          let(:bson) { "#{pattern}#{BSON::NULL_BYTE}ilmsux#{BSON::NULL_BYTE}" }

          it "serializes the options in alphabetical order" do
            expect(serialized).to eq(bson)
          end
        end
      end
    end
  end

  describe "#compile" do

    let(:obj) { Regexp.from_bson(io) }
    let(:io) { BSON::ByteBuffer.new(bson) }
    let(:ruby_regexp) { obj.compile }

    it "sets the pattern on the Ruby Regexp object" do
      expect(obj.pattern).to eq(ruby_regexp.source)
    end

    context "when there are no options set" do

      it "does not set any options on the Ruby Regexp object" do
        expect(ruby_regexp.options).to eq(0)
      end
    end

    context "when there are options set" do

      context "when there is the i ignorecase option" do

        let(:options) { 'i' }

        it "sets the i option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(::Regexp::IGNORECASE)
        end
      end

      context "when there is the l locale dependent option" do

        let(:options) { 'l' }

        it "does not set an option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(0)
        end
      end

      context "when there is the m multiline option" do

        let(:options) { 'm' }

        it "does not set an option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(0)
        end
      end

      context "when there is the s dotall option" do

        let(:options) { 's' }

        # s in a bson regex maps to a Ruby Multiline Regexp option
        it "sets the m option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(::Regexp::MULTILINE)
        end
      end

      context "when there is the u match unicode option" do

        let(:options) { 'u' }

        it "does not set an option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(0)
        end
      end

      context "when there is the x verbose option" do

        let(:options) { 'x' }

        it "sets the x option on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(::Regexp::EXTENDED)
        end
      end

      context "when all options are set" do

        let(:options) { 'ilmsux' }

        # s in a bson regex maps to a Ruby Multiline Regexp option
        it "sets the i, m, and x options on the Ruby Regexp object" do
          expect(ruby_regexp.options).to eq(::Regexp::IGNORECASE | ::Regexp::MULTILINE | ::Regexp::EXTENDED)
        end
      end
    end
  end

  context "when a Regexp::Raw object is roundtripped" do

    let(:obj) { Regexp::Raw.new(pattern, options) }
    let(:serialized) { obj.to_bson.to_s }
    let(:roundtripped) { Regexp.from_bson(BSON::ByteBuffer.new(serialized)) }

    it "roundtrips the pattern" do
      expect(roundtripped.pattern).to eq(pattern)
    end

    context "when there are no options" do

      let(:options) { '' }

      it "does not set any options on the roundtripped Regexp::Raw object" do
        expect(roundtripped.options).to eq(options)
      end
    end

    context "when there are options set" do

      context "when there is the i ignorecase option" do

        let(:options) { 'i' }

        it "sets the i option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when there is the l locale dependent option" do

        let(:options) { 'l' }

        it "sets the l option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when there is the m multiline option" do

        let(:options) { 'm' }

        it "sets the m option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when there is the s dotall option" do

        let(:options) { 's' }

        it "sets the s option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when there is the u match unicode option" do

        let(:options) { 'u' }

        it "sets the u option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when there is the x verbose option" do

        let(:options) { 'x' }

        it "sets the x option on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end
      end

      context "when all options are set" do

        let(:options) { 'ilmsux' }

        it "sets all the options on the roundtripped Regexp::Raw object" do
          expect(roundtripped.options).to eq(options)
        end

        context "when the options are passed in not in alphabetical order" do

          let(:options) { 'sumlxi' }

          it "sets all the options on the roundtripped Regexp::Raw object in order" do
            expect(roundtripped.options).to eq(options.chars.sort.join)
          end
        end
      end
    end
  end

  describe 'yaml loading' do
    let(:regexp) { described_class.new('hello.world', 's') }

    it 'round-trips' do
      actual = if YAML.respond_to?(:unsafe_load)
        # In psych >= 4.0.0 `load` is basically an alias to `safe_load`,
        # which will fail here.
        YAML.unsafe_load(regexp.to_yaml)
      else
        YAML.load(regexp.to_yaml)
      end
      actual.pattern.should == 'hello.world'
      actual.options.should == 's'
      actual.compile.should =~ "hello\nworld"
    end
  end
end
