module Nokogiri
  module CSS
    class XPathVisitor
      def visit_function node
        #  note that nth-child and nth-last-child are preprocessed in css/node.rb.
        case node.value.first
        when /^text\(/
          'child::text()'
        when /^self\(/
          "self::#{node.value[1]}"
        when /^(eq|nth|nth-of-type|nth-child)\(/
          if node.value[1].is_a?(Nokogiri::CSS::Node) and node.value[1].type == :AN_PLUS_B
            an_plus_b(node.value[1])
          else
            "position() = " + node.value[1]
          end
        when /^(first|first-of-type)\(/
          "position() = 1"
        when /^(last|last-of-type)\(/
          "position() = last()"
        when /^(nth-last-child|nth-last-of-type)\(/
          "position() = last() - #{node.value[1]}"
        when /^contains\(/
          "contains(., #{node.value[1]})"
        when /^gt\(/
          "position() > #{node.value[1]}"
        when /^only-child\(/
          "last() = 1"
        else
          node.value.first + ')'
        end
      end

      def visit_not node
        'not(' + node.value.first.accept(self) + ')'
      end

      def visit_preceding_selector node
        node.value.last.accept(self) +
          '[preceding-sibling::' +
          node.value.first.accept(self) +
          ']'
      end

      def visit_direct_adjacent_selector node
         node.value.last.accept(self) +
           '[preceding-sibling::' +
           node.value.first.accept(self) +
           '][position()=1]'
      end

      def visit_id node
        node.value.first =~ /^#(.*)$/
        "@id = '#{$1}'"
      end

      def visit_attribute_condition node
        attribute = if (node.value.first.type == :FUNCTION) or (node.value.first.value.first =~ /::/)
                      ''
                    else
                      '@'
                    end
        attribute += node.value.first.accept(self)

        # Support non-standard css
        attribute.gsub!(/^@@/, '@')

        return attribute unless node.value.length == 3

        value = node.value.last
        value = "'#{value}'" if value !~ /^['"]/

        case node.value[1]
        when '*='
          "contains(#{attribute}, #{value})"
        when '^='
          "starts-with(#{attribute}, #{value})"
        when '|='
          "#{attribute} = #{value} or starts-with(#{attribute}, concat(#{value}, '-'))"
        when '~='
          "contains(concat(\" \", #{attribute}, \" \"),concat(\" \", #{value}, \" \"))"
        when '$='
          "substring(#{attribute}, string-length(#{attribute}) - " +
            "string-length(#{value}) + 1, string-length(#{value})) = #{value}"
        else
          attribute + " #{node.value[1]} " + "#{value}"
        end
      end

      def visit_pseudo_class node
        if node.value.first.is_a?(Nokogiri::CSS::Node) and node.value.first.type == :FUNCTION
          node.value.first.accept(self)
        else
          case node.value.first
          when "first" then "position() = 1"
          when "last" then "position() = last()"
          when "first-of-type" then "position() = 1"
          when "last-of-type" then "position() = last()"
          when "only-of-type" then "last() = 1"
          when "empty" then "not(node())"
          when "parent" then "node()"
          else
            '1 = 1'
          end
        end
      end

      def visit_class_condition node
        "contains(concat(' ', @class, ' '),concat(' ', '#{node.value.first}', ' '))"
      end

      def visit_combinator node
        node.value.first.accept(self) + ' and ' +
        node.value.last.accept(self)
      end

      def visit_conditional_selector node
        node.value.first.accept(self) + '[' +
        node.value.last.accept(self) + ']'
      end

      def visit_descendant_selector node
        node.value.first.accept(self) +
        '//' +
        node.value.last.accept(self)
      end

      def visit_child_selector node
        node.value.first.accept(self) +
        '/' +
        node.value.last.accept(self)
      end

      def visit_element_name node
        node.value.first
      end

      def accept node
        node.accept(self)
      end

    private
      def an_plus_b node
        raise ArgumentError, "expected an+b node to contain 4 tokens, but is #{node.value.inspect}" unless node.value.size == 4

        a = node.value[0].to_i
        b = node.value[3].to_i

        if (b == 0)
          return "(position() mod #{a}) = 0"
        else
          compare = (a < 0) ? "<=" : ">="
          return "(position() #{compare} #{b}) and (((position()-#{b}) mod #{a.abs}) = 0)"
        end
      end

    end
  end
end
