# encoding: utf-8
module Journey
  module Visitors
    class Visitor # :nodoc:
      DISPATCH_CACHE = Hash.new { |h,k|
        h[k] = "visit_#{k}"
      }

      def accept node
        visit node
      end

      private
      def visit node
        send DISPATCH_CACHE[node.type], node
      end

      def binary node
        visit node.left
        visit node.right
      end
      def visit_CAT(n); binary(n); end

      def nary node
        node.children.each { |c| visit c }
      end
      def visit_OR(n); nary(n); end

      def unary node
        visit node.left
      end
      def visit_GROUP(n); unary(n); end
      def visit_STAR(n); unary(n); end

      def terminal node; end
      %w{ LITERAL SYMBOL SLASH DOT }.each do |t|
        class_eval %{ def visit_#{t}(n); terminal(n); end }, __FILE__, __LINE__
      end
    end

    ##
    # Loop through the requirements AST
    class Each < Visitor # :nodoc:
      attr_reader :block

      def initialize block
        @block = block
      end

      def visit node
        super
        block.call node
      end
    end

    class String < Visitor
      private

      def binary node
        [visit(node.left), visit(node.right)].join
      end

      def nary node
        node.children.map { |c| visit c }.join '|'
      end

      def terminal node
        node.left
      end

      def visit_STAR node
        "*" + super
      end

      def visit_GROUP node
        "(#{visit node.left})"
      end
    end

    ###
    # Used for formatting urls (url_for)
    class Formatter < Visitor
      attr_reader :options, :consumed

      def initialize options
        @options  = options
        @consumed = {}
      end

      private
      def visit_GROUP node
        if consumed == options
          nil
        else
          route = visit node.left
          route.include?("\0") ? nil : route
        end
      end

      def terminal node
        node.left
      end

      def binary node
        [visit(node.left), visit(node.right)].join
      end

      def nary node
        node.children.map { |c| visit c }.join
      end

      def visit_SYMBOL node
        key = node.to_sym

        if value = options[key]
          consumed[key] = value
          Router::Utils.escape_path(value)
        else
          "\0"
        end
      end
    end

    class Dot < Visitor
      def initialize
        @nodes = []
        @edges = []
      end

      def accept node
        super
        <<-eodot
digraph parse_tree {
  size="8,5"
  node [shape = none];
  edge [dir = none];
  #{@nodes.join "\n"}
  #{@edges.join("\n")}
}
        eodot
      end

      private
      def binary node
        node.children.each do |c|
          @edges << "#{node.object_id} -> #{c.object_id};"
        end
        super
      end

      def nary node
        node.children.each do |c|
          @edges << "#{node.object_id} -> #{c.object_id};"
        end
        super
      end

      def unary node
        @edges << "#{node.object_id} -> #{node.left.object_id};"
        super
      end

      def visit_GROUP node
        @nodes << "#{node.object_id} [label=\"()\"];"
        super
      end

      def visit_CAT node
        @nodes << "#{node.object_id} [label=\"○\"];"
        super
      end

      def visit_STAR node
        @nodes << "#{node.object_id} [label=\"*\"];"
        super
      end

      def visit_OR node
        @nodes << "#{node.object_id} [label=\"|\"];"
        super
      end

      def terminal node
        value = node.left

        @nodes << "#{node.object_id} [label=\"#{value}\"];"
      end
    end
  end
end
