# There might be a nicer way to decorate this class but with my limited knowledge could only get this handler
# to be applied after the default constant handler by inheriting from the default constant handler.
# This is required so that the value assigned from the transform is not overridden in the registry by the
# default handler
class YARD::Handlers::Ruby::ConstantTransformHandler < YARD::Handlers::Ruby::ConstantHandler
  include YARD::Handlers::Ruby::StructHandlerMethods
  handles :assign

  namespace_only

  process do
    begin
      if statement[1][0][0] == "Transform"
        name = statement[0][0][0]
        # Move the docstring to the transform statement
        statement[1].docstring = statement.docstring
        # Set the docstring on the constant to reference the transform that will be processed
        statement.docstring = "Reference to {#{YARD::CodeObjects::Cucumber::CUCUMBER_STEPTRANSFORM_NAMESPACE}::#{name} transform}"
        value = statement[1][1].source
        value = substitute(value)
        value = convert_captures(strip_anchors(value))
        instance = register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value }
        # specify the owner so that the transform can use the registered constant's name
        parse_block(statement[1], {:owner => instance})
      elsif statement[1][0][0] == "ParameterType"
        name = statement[0][0][0]
        # Move the docstring to the transform statement
        statement[1].docstring = statement.docstring
        # Set the docstring on the constant to reference the transform that will be processed
        statement.docstring = "Reference to {#{YARD::CodeObjects::Cucumber::CUCUMBER_STEPTRANSFORM_NAMESPACE}::#{name} transform}"

        value = find(statement, :label, 'regexp:').parent.children[1].source

        value = substitute(value)
        value = convert_captures(strip_anchors(value))
        instance = register ConstantObject.new(namespace, name) {|o| o.source = statement; o.value = value }
        # specify the owner so that the transform can use the registered constant's name
        parse_block(statement[1], {:owner => instance})
      end
    rescue
      # This supresses any errors where any of the statement elements are out of bounds. 
      # In this case the or in cases where the object being assigned is not a Transform
      # the default constant handler will already have performed the relevant action
    end
  end

  private

  def find(node, node_type, value)
    node.traverse { |child| return(child) if node_type == child.type && child.source == value }
    self
  end

  # Cucumber's Transform object overrides the to_s function and strips
  # the anchor tags ^$ and any captures so that id it is interpolated in a step definition
  # the it can appear anywhere in the step without being effected by position or captures
  def convert_captures(regexp_source)
    regexp_source
      .gsub(/(\()(?!\?[<:=!])/,'(?:')
      .gsub(/(\(\?<)(?![=!])/,'(?:<')
  end

  def strip_anchors(regexp_source)
    regexp_source.
      gsub(/(^\(\/|\/\)$)/, '').
      gsub(/(^\^|\$$)/, '')
  end

  # Support for interpolation in the Transform's Regex
  def substitute(data)
    until (nested = constants_from_value(data)).empty?
      nested.each {|n| data.gsub!(value_regex(n),find_value_for_constant(n)) }
    end
    data
  end

  def constants_from_value(data)
    escape_pattern = /#\{\s*(\w+)\s*\}/
    data.scan(escape_pattern).flatten.collect { |value| value.strip }
  end

  # Return a regex of the value
  def value_regex(value)
    /#\{\s*#{value}\s*\}/
  end

  def find_value_for_constant(name)
    constant = YARD::Registry.all(:constant).find{|c| c.name == name.to_sym }
    log.warn "ConstantTransformHandler#find_value_for_constant : Could not find the CONSTANT [#{name}] using the string value." unless constant
    constant ? strip_regex_from(constant.value) : name
  end

  # Step the regex starting / and ending / from the value
  def strip_regex_from(value)
    value.gsub(/^\/|\/$/,'')
  end

end
