require 'ruby_speech/xml/language'

module RubySpeech
  module GRXML
    ##
    #
    # The item element is one of the valid expansion elements for the SGR rule element
    #
    #   http://www.w3.org/TR/speech-grammar/#S2.4 --> XML Form
    #
    # The item element has four (optional) attributes: weight, repeat, repeat-prob, and xml:lang (language identifier)
    #
    #   http://www.w3.org/TR/speech-grammar/#S2.4.1
    #   http://www.w3.org/TR/speech-grammar/#S2.3
    #
    # A weight may be optionally provided for any number of alternatives in an alternative expansion. Weights are simple positive floating point values without exponentials. Legal formats are "n", "n.", ".n" and "n.n" where "n" is a sequence of one or many digits.
    #
    # A weight is nominally a multiplying factor in the likelihood domain of a speech recognition search. A weight of 1.0 is equivalent to providing no weight at all. A weight greater than "1.0" positively biases the alternative and a weight less than "1.0" negatively biases the alternative.
    #
    # repeat has several valid values...
    #
    # Any repeated legal rule expansion is itself a legal rule expansion.
    #
    # Operators are provided that define a legal rule expansion as being another sub-expansion that is optional, that is repeated zero or more times, that is repeated one or more times, or that is repeated some range of times.
    #
    # repeat probability (repeat-prob)  indicates the probability of successive repetition of the repeated expansion.  It is ignored if repeat is not specified
    #
    # xml:lang declares declaration declares the language of the grammar section for the item element just as xml:lang in the <grammar> element declares for the entire document
    #
    class Item < Element

      %w{
        rule
        one_of
        ruleref
        tag
        token
      }.each { |f| require "ruby_speech/grxml/#{f}" }

      include XML::Language

      Inf = 1.0 / 0.0

      register :item

      VALID_CHILD_TYPES = [Nokogiri::XML::Element, Nokogiri::XML::Text, OneOf, Item, String, Ruleref, Tag, Token].freeze

      ##
      #
      # The optional weight attribute
      #
      # @return [Float]
      #
      def weight
        read_attr :weight, :to_f
      end

      ##
      #
      # The weight attribute takes a positive (floating point) number
      # NOTE: the standard says a format of "n" is valid (eg. an Integer)
      # TODO: possibly support string and check to see if its a valid digit with regex...
      #
      # @param [Numeric] w
      #
      def weight=(w)
        raise ArgumentError, "A Item's weight attribute must be a positive floating point number" unless w.to_s.match(/[^0-9\.]/) == nil and w.to_f >= 0
        self[:weight] = w
      end

      ##
      #
      # The repeat attribute
      #
      # @return [String]
      #
      def repeat
        repeat = read_attr :repeat
        return nil unless repeat
        if repeat.include?('-')
          min, max = repeat.split('-').map &:to_i
          (min || 0)..(max || Inf)
        else
          repeat.to_i
        end
      end

      ##
      #
      # TODO: Raise ArgumentError after doing checking.  See
      #       http://www.w3.org/TR/speech-grammar/#S2.5
      #
      # @param [String] r
      #
      def repeat=(r)
        r = "#{r.min}-#{r.max unless r.max == Inf}" if r.is_a?(Range)
        r = r.to_s
        error = ArgumentError.new "A Item's repeat must be 0 or a positive integer"

        raise error unless r.match(/[^0-9-]/) == nil and r.scan("-").size <= 1

        raise error if case di = r.index('-')
        when nil
          r.to_i < 0  # must be 0 or a positive number
        when 0
          true  # negative numbers are illegal
        else
          if di == r.length - 1  # repeat 'm' or more times, m must be 0 or a positive number
            r[0, r.length - 1].to_i < 0
          else  # verify range m,n is valid
            m, n = r.split('-').map &:to_i
            m < 0 || n < m
          end
        end
        self[:repeat] = r
      end

      ##
      #
      # The optional repeat-prob attribute
      #
      # @return [Float]
      #
      def repeat_prob
        read_attr :'repeat-prob', :to_f
      end

      ##
      # @param [Numeric] ia
      #
      def repeat_prob=(rp)
        raise ArgumentError, "A Item's repeat probablity attribute must be a floating point number between 0.0 and 1.0" unless rp.to_s.match(/[^0-9\.]/) == nil and rp.to_f >= 0 and rp.to_f <= 1.0
        self['repeat-prob'] = rp
      end

      def <<(arg)
        raise InvalidChildError, "A Item can only accept String, Ruleref, Tag or Token as children" unless VALID_CHILD_TYPES.include? arg.class
        super
      end

      def eql?(o)
        super o, :weight, :repeat
      end

      def regexp_content # :nodoc:
        case repeat
        when Range
          "#{super}{#{repeat.min},#{repeat.max unless repeat.max == Inf}}"
        when Integer
          "#{super}{#{repeat}}"
        else
          super
        end
      end
    end # Item
  end # GRXML
end # RubySpeech
