require 'spec_helper'

module RubySpeech
  describe GRXML do
    describe ".from_uri" do
      context "with a builtin URI" do
        it "should fetch a simple builtin grammar by type" do
          expect(subject.from_uri("builtin:dtmf/phone")).to eq(GRXML::Builtins.phone)
        end

        it "should fetch a parameterized builtin grammar" do
          expect(subject.from_uri("builtin:dtmf/boolean?y=3;n=4")).to eq(GRXML::Builtins.boolean(y: 3, n: 4))
        end

        context "for speech" do
          it "should raise ArgumentError" do
            expect { subject.from_uri("builtin:speech/phone") }.to raise_error(ArgumentError, /Only DTMF/)
          end
        end

        context "that doesn't exist" do
          it "should raise ArgumentError" do
            expect { subject.from_uri("builtin:dtmf/foobar") }.to raise_error(ArgumentError, /invalid/)
          end
        end
      end

      context "with an http URI" do
        it "should raise ArgumentError" do
          expect { subject.from_uri("http://foo.com/grammar.grxml") }.to raise_error(ArgumentError, /builtin/)
        end
      end
    end

    describe "#draw" do
      let(:doc) { Nokogiri::XML::Document.new }

      it "should create a GRXML document" do
        expect(GRXML.draw).to eq(GRXML::Grammar.new(doc))
        expect(GRXML.draw.document.xpath('ns:grammar', ns: 'http://www.w3.org/2001/06/grammar').size).to eq(1)
      end

      context "with a root rule name specified but not found" do
        it "should raise an error" do
          expect do
            GRXML.draw :root => 'foo' do
              rule :id => 'bar' do
                '6'
              end
            end
          end.to raise_error(GRXML::InvalidChildError, "A GRXML document must have a rule matching the root rule name")
        end
      end

      # TODO: Maybe GRXML#draw should create a Rule to pass the string
      describe "when the return value of the block is a string" do
        it "should be inserted into the document" do
          expect { GRXML.draw { "Hello Fred" }}.to raise_error(GRXML::InvalidChildError, "A Grammar can only accept Rule and Tag as children")
        end
      end

      it "should allow other GRXML elements to be inserted in the document" do
        drawn_doc = GRXML.draw(:mode => :voice, :root => 'main') { rule :id => :main, :content => "Hello Fred" }

        expected_doc = GRXML::Grammar.new(doc, :mode => :voice, :root => 'main')
        rule = GRXML::Rule.new(doc, :id => "main")
        rule << "Hello Fred"
        expected_doc << rule
        expect(drawn_doc).to eq(expected_doc)
      end

      it "should allow accessing methods defined outside the block" do
        def foo
          'bar'
        end

        drawn_doc = GRXML.draw do
          rule :id => foo
        end

        expected_doc = GRXML::Grammar.new doc
        rule = GRXML::Rule.new(doc, :id => foo)
        expected_doc << rule
        expect(drawn_doc).to eq(expected_doc)
      end

      it "should raise error if given an empty rule" do
        pending 'Reject empty rules -- http://www.w3.org/TR/2002/CR-speech-grammar-20020626/#S3.1 http://www.w3.org/Voice/2003/srgs-ir/test/rule-no-empty.grxml'
        expect { GRXML.draw { rule :id => 'main' }}.to raise_error
      end

      it "should allow nested block return values" do
        drawn_doc = RubySpeech::GRXML.draw do
          rule :scope => 'public', :id => :main do
            "Hello Fred"
          end
        end
        expected_doc = GRXML::Grammar.new doc
        expected_doc << GRXML::Rule.new(doc, :scope => :public, :id => :main, :content => "Hello Fred")
        expect(drawn_doc).to eq(expected_doc)
      end

      it "should allow nested GRXML elements" do
        drawn_doc = RubySpeech::GRXML.draw do
          rule :id => :main, :scope => 'public' do
            string "Hello Fred. I like ninjas and pirates"
            one_of do
              item :content => "ninja"
              item :content => "pirate"
            end
          end
        end
        rule = GRXML::Rule.new(doc, :id => :main, :scope => 'public', :content => "Hello Fred. I like ninjas and pirates")
        oneof = GRXML::OneOf.new doc
        oneof << GRXML::Item.new(doc, :content => "ninja")
        oneof << GRXML::Item.new(doc, :content => "pirate")
        rule << oneof
        expected_doc = GRXML::Grammar.new doc
        expected_doc << rule
        expect(drawn_doc).to eq(expected_doc)
      end

      # TODO: maybe turn a rule embedded in anthoer rule into a ruleref??
      describe "embedding" do
        context "GRXML documents" do
          let :doc1 do
            RubySpeech::GRXML.draw :mode => :dtmf, :root => 'digits' do
              rule :id => :digits do
                one_of do
                  item { "1" }
                  item { "2" }
                end
              end
            end
          end

          let :doc2 do
            doc = doc1
            RubySpeech::GRXML.draw :mode => :dtmf do
              embed doc
              rule :id => :main do
                "Hello Fred"
              end
            end
          end

          let :expected_doc do
            RubySpeech::GRXML.draw :mode => :dtmf do
              rule :id => :digits do
                one_of do
                 item :content => "1"
                 item :content => "2"
                end
              end
              rule :id => :main, :content => 'Hello Fred'
            end
          end

          it "should embed the document" do
            expect(doc2).to eq(expected_doc)
          end

          context "of different modes (dtmf in voice or vice-versa)" do
            let :voice_doc do
              GRXML.draw :mode => :voice do
                embed dtmf_doc
              end
            end

            let :dtmf_doc do
              GRXML.draw :mode => :dtmf do
                rule do
                  '6'
                end
              end
            end

            it "should raise an exception" do
              expect { voice_doc }.to raise_error(GRXML::InvalidChildError, "Embedded grammars must have the same mode")
            end
          end
        end

        it "GRXML elements" do
          element = GRXML::Item.new doc, :content => "HELLO?"

          doc = RubySpeech::GRXML.draw do
            rule :id => :main, :scope => 'public' do
              embed element
            end
          end

          expected_doc = RubySpeech::GRXML.draw do
            rule :id => :main, :scope => 'public' do
              item do
                "HELLO?"
              end
            end
          end

          expect(doc).to eq(expected_doc)
        end

        it "strings" do
          string = "How now, brown cow?"

          doc = RubySpeech::GRXML.draw do
            rule :id => :main, :scope => 'public' do
              embed string
            end
          end

          expected_doc = RubySpeech::GRXML.draw do
            rule :id => :main, :scope => 'public' do
              string "How now, brown cow?"
            end
          end

          expect(doc).to eq(expected_doc)
        end
      end

      it "should properly escape string input" do
        drawn_doc = RubySpeech::GRXML.draw do
          rule { string "I <3 nachos." }
          rule { "I <3 nachos." }
          rule { 'I <3 nachos.' }
        end
        expected_doc = GRXML::Grammar.new doc
        3.times do
          expected_doc << GRXML::Rule.new(doc, :native_content => "I <3 nachos.")
        end
        expect(drawn_doc).to eq(expected_doc)
      end

      # TODO: verfify rule is in document if named in a ruleref
      # TODO: ruleref must have named rule id

      it "should allow all permutations of possible nested GRXML elements" do
        drawn_doc = RubySpeech::GRXML.draw do
          rule :id => "hello" do
            string "HELLO?"
            item :weight => 2.5
            one_of do
              item { string 1 }
              item { "2" }
            end
            ruleref :uri => '#test'
            item { "last" }
          end
          rule :id => "test" do
            string "TESTING"
          end
          rule :id => :hello2 do
            item :weight => 5.5 do
              "hello"
            end
            string "H...E...L...L...O?"
            token { "test token" }
            tag { }
            item { "" }
            one_of { item { "single item" } }
          end
        end
        expected_doc = GRXML::Grammar.new doc
        rule = GRXML::Rule.new(doc, :id => "hello", :content => "HELLO?")
        rule << GRXML::Item.new(doc, :weight => 2.5)
        oneof = GRXML::OneOf.new doc
        1.upto(2) { |d| oneof << GRXML::Item.new(doc, :content => d.to_s) }
        rule << oneof
        rule << GRXML::Ruleref.new(doc, :uri => '#test')
        rule << GRXML::Item.new(doc, :content => "last")
        expected_doc << rule

        rule = GRXML::Rule.new(doc, :id => "test", :content => "TESTING")
        expected_doc << rule

        rule = GRXML::Rule.new(doc, :id => "hello2")
        rule << GRXML::Item.new(doc, :weight => 5.5, :content => "hello")
        rule << "H...E...L...L...O?"
        rule << GRXML::Token.new(doc, :content => "test token")
        rule << GRXML::Tag.new(doc)
        rule << GRXML::Item.new(doc)
        oneof = GRXML::OneOf.new doc
        oneof << GRXML::Item.new(doc, :content => "single item")
        rule << oneof
        expected_doc << rule
        expect(drawn_doc).to eq(expected_doc)
      end

      describe "importing nested tags" do
        let(:doc) { Nokogiri::XML::Document.new }
        let(:item) { GRXML::Item.new(doc, :weight => 1.5, :content => "Are you a pirate or ninja?") }
        let(:string) { "Hello Fred. I like pirates and ninjas " }
        let :rule do
          GRXML::Rule.new(doc, :id => :main, :scope => 'public', :content => string).tap do |rule|
            rule << item
          end
        end

        let :document do
          GRXML::Grammar.new(doc).tap { |doc| doc << rule }.to_s
        end

        let(:import) { GRXML.import document }

        subject { import }

        it "should work" do
          expect { subject }.not_to raise_error
        end

        it { is_expected.to be_a GRXML::Grammar }

        its(:children) { should == [rule] }

        describe "rule" do
          subject { import.children.first }
          its(:children) { should == [string,item] }
        end
      end

      it "should allow finding direct children of a particular type, matching certain attributes" do
        item = GRXML::Item.new doc
        item1 = GRXML::Item.new doc, :weight => 0.5
        item11 = GRXML::Item.new doc, :weight => 0.5
        item1 << item11
        item << item1
        item2 = GRXML::Item.new doc, :weight => 0.7
        item << item2
        tag = GRXML::Tag.new doc
        item << tag

        expect(item.children(:item, :weight => 0.5)).to eq([item1])
      end

      it "should be able to traverse up the tree" do
        grammar = GRXML.draw do
          rule :id => 'one' do
            item do
              'foobar'
            end
          end
        end

        rule = grammar.children.first
        expect(rule.parent).to eq(grammar)

        item = rule.children.first
        expect(item.parent).to eq(rule)

        text = item.nokogiri_children.first
        expect(text.parent).to eq(item)
      end
    end
  end # GRXML
end # RubySpeech
