class Rtml::Widgets::ScreenVariants < Rtml::Widget
  include Rtml::TmlizedConditions
  affects :screen
  entry_point :variant, :next, :next_screen, :goto
  disable_subprocessing! # we don't support it

  # Builds a TML next element. If the element already exists, its URI is changed.
  # If an :if or :unless option is provided, builds a variant instead.
  # See Rtml::Rules::ExpressionProcessing for information about the :if and :unless options.
  # Other options include :timeout, which specifies a timeout interval, or :key, which specifies
  # a trigger key.
  # Examples:
  #   screen :main do
  #     next_screen :terminate_loop
  #     next_screen :loop_again, :if => 'tmlvar:counter < tmlvar:loop_count'
  #   end
  def next(uri, options = {})
    unless options.empty?
      variant process_variant_options(options.merge(:uri => uri))
    else
      next_element = (parent / 'next').first
      if next_element
        next_element.property('uri',  uri)
      else
        next_opts = { :uri => uri }
        next_opts[:timeout] = options[:timeout] if options.key?(:timeout)
        parent.property('next', '')
        parent.build(:next, next_opts)
      end
    end
  end
  alias next_screen next
  alias goto next

  # Builds a TML variant element. If the corresponding NEXT element does not exist, then it is created. Its
  # attributes are inferred from the screen's NEXT property, if any. If the NEXT element neither exists nor
  # can be inferred, its URI will be set to raise an assertion error (#assert). If a variant already exists
  # with the same conditions, its URI will be replaced.
  # Examples:
  #   screen :main, :next => :terminate_loop do
  #     variant :uri => :loop_again, :lo => 'tmlvar:counter', :op => 'less', :ro => 'tmlvar:loop_count'
  #   end
  def variant(options)
    options = options.with_indifferent_access
    validate_options(options, :variant)
    default_next = parent.property('next')
    next_element = (parent / 'next').first
    unless next_element
      if default_next.blank?
        next_element = parent.build(:next, :uri => 'assert')
#        raise Rtml::Errors::ScreenVariantError, "NEXT element does not exist, and could not infer destination"
      else
        next_element ||= parent.build(:next, :uri => default_next) # if it doesn't exist, create it.
      end
    end
    variants = (next_element / "variant")
    variants.each do |variant|
      # If variant contains the same conditions, replace the URI and return.
      if (variant.property('lo') == options[:lo] && variant.property('ro') == options[:ro] && variant.property('op') == options[:op]) ||
          (options.key?('key') && variant.property('key') == options[:key]) ||
          (options.key?('timeout') && variant.property('timeout') == options[:timeout])
        variant.property('uri', options[:uri])
        return variant
      end
    end
    next_element.build(:variant, options)
  end

  private
  def process_variant_options(options)
    ret = {}
    if options[:lo] || options[:ro] || options[:op]
      options.delete(:unless)
      options[:if] = [options.delete(:lo), options.delete(:op), options.delete(:ro)]
    end
    if options[:unless] || options[:if]
      ret[:lo], ret[:op], ret[:ro] = process_condition((options[:unless] ? :unless : :if),
                                                       options[:unless] || options[:if])
    end
    [:uri, :timeout, :key, :format].each { |k| ret[k] = options[k] if options.key?(k) }
    ret
  end

  def process_condition(condition_type, condition)
    condition = condition.split if condition.kind_of?(String)
    tmlize_condition(*(condition + [condition_type]))
  end

  def validate_options(options, type)
    case type
      when :variant
        if options.key?(:uri)
          options_valid = if options.key?(:lo) && options.key?(:ro) && options.key?(:op)
            true
          elsif not options.key?(:lo) && options.key?(:ro) && options.key?(:op)
            options.key?(:key) || options.key?(:timeout)
          end
          unless options_valid
            if options.key?(:lo) || options.key?(:op) || options.key?(:op)
              raise ArgumentError, "Expected :lo, :ro, :op options; see TML docs"
            else
              raise ArgumentError, "Expected condition or :key or :timeout options; see TML docs"
            end
          end
        else
          raise ArgumentError, "All variants must include a :uri option"
        end
    end
  end
end