module Watir
  module Generator
    class Base::Visitor < WebIDL::RubySexpVisitor

      def initialize
        super

        # When an interface has multiple IDL definitions in the spec, the inheritance is sometimes
        # not repeated. So we'll keep track ourselves.
        @inheritance_map = {}

        @already_defined = []
      end

      #
      # WebIDL visitor interface
      #

      def visit_interface(interface)
        name   = interface.name
        parent = interface.inherits.first

        $stderr.puts name
        return if name !~ interface_regexp || name =~ /(Collection|Document)$/

        if force_inheritance.keys.include?(name)
          parent = force_inheritance[name]
        elsif parent
          @inheritance_map[name] ||= parent.name
          parent = parent.name
        else
          parent = @inheritance_map[name] || return
        end

        [ :scope,
          [:block,
            element_class(interface.name, extract_attributes(interface), parent),
            collection_class(interface.name)
          ]
        ]
      end

      def visit_module(mod)
        # ignored
      end

      def visit_implements_statement(stmt)
        # ignored
      end

      # TODO: do everything in the visitor somehow?
      # problem is we lack the tag name info while walking the interface AST
      # #
      # # Watir generator visitor interface
      # #
      #
      # def visit_tag(tag_name, interface_name)
      #   tag_string       = tag.inspect
      #   singular         = Util.paramify(classify_regexp, tag)
      #   plural           = singular.pluralize
      #   element_class    = Util.classify(classify_regexp, interfaces.first.name)
      #   collection_class = "#{element_class}Collection"
      #
      #   [:defn,
      #    :a,
      #    [:args, :"*args"],
      #    [:scope,
      #     [:block,
      #      [:call,
      #       [:const, :Anchor],
      #       :new,
      #       [:arglist,
      #        [:self],
      #        [:call,
      #         [:call, nil, :extract_selector, [:arglist, [:lvar, :args]]],
      #         :merge,
      #         [:arglist, [:hash, [:lit, :tag_name], [:str, "a"]]]]]]]]]
      # end

      private

      def element_class(name, attributes, parent)
        [:class, Util.classify(classify_regexp, name), [:const, Util.classify(classify_regexp, parent)],
          *attribute_calls(attributes)
        ]
      end

      def extract_attributes(interface)
        members = interface.members
        members += interface.implements.flat_map(&:members)

        members.select { |e| e.kind_of?(WebIDL::Ast::Attribute) }
      end

      def collection_class(name)
        return if @already_defined.include?(name)
        @already_defined << name
        name = Util.classify(classify_regexp, name)

        [:class, "#{name}Collection", [:const, :ElementCollection],
          [:scope,
            [:defn, :element_class,
              [:args],
              [:scope,
                [:block, [:const, name]]
              ]
            ]
          ]
        ]
      end

      def attribute_calls(attributes)
        attributes.map do |attribute|
          call(:attribute, [
            [:lit, ruby_type_for(attribute.type)],
            [:lit, ruby_method_name_for(attribute)],
            [:lit, attribute.name.to_sym]
          ])
        end
      end

      def call(name, args)
        [:call, nil, name.to_sym, [:arglist] + args]
      end

      def ruby_method_name_for(attribute)
        str = attribute.name.snake_case

        if attribute.type.name == :Boolean
          str = $1 if str =~ /^is_(.+)/
          str << '?'
        end

        str = 'for' if str == 'html_for'

        str.to_sym
      end

      def ruby_type_for(type)
        case type.name.to_s
        when 'DOMString', 'any'
          String
        when 'UnsignedLong', 'Long', 'Integer', 'Short', 'UnsignedShort',
             'SVGAnimatedLength'
          Fixnum
        when 'Float', /.*Double$/
          Float
        when 'Boolean'
          'Boolean'
        when 'WindowProxy', 'ValidityState', 'TimeRanges', 'Location',
             'Any', 'TimedTrackArray', 'TimedTrack', 'TextTrackArray', 'TextTrack',
             /Media.+/, 'TextTrackKind', 'Function', /.*EventHandler$/,
             'Document', 'DocumentFragment', 'DOMTokenList', 'DOMSettableTokenList',
             'DOMStringMap', 'HTMLPropertiesCollection', /HTML.*Element/, /HTML.*Collection/,
             'CSSStyleDeclaration',  /.+List$/, 'Date', 'Element', /DOM.+ReadOnly/,
             /SVGAnimated.+/, /SVG.*Element/, /SVG.*Collection/, 'SVGViewSpec'
          # probably completely wrong.
          String
        else
          raise "unknown type: #{type.name}"
        end
      end

    end # Visitor
  end # Generator
end # Watir
