module SketchUpYARD
  module Stubs
    class AutoLoadGenerator

      # @param [YARD::CodeObject::Base] namespace_objects
      # @param [IO] out
      def generate(namespace_objects, out)
        dependencies = resolve_dependencies(namespace_objects)
        out.puts "# This file is auto-generated by the `thor stubs` command."
        required = Set.new
        # Top level is special case.
        top_level_path = File.join(sketchup_stubs_path, '_top_level.rb')
        out.puts "require '#{top_level_path}'"
        # The rest is resolved recursively.
        dependencies.each { |object|
          recurse_require(object, required, out)
        }
      end

      private

      class Node

        attr_reader :object
        attr_reader :dependencies

        def initialize(object)
          @object = object
          @dependencies = SortedSet.new
        end

        def root?
          @object.namespace.root?
        end

        def path
          @object.path
        end

        def namespace
          @object.namespace
        end

        def <<(object)
          @dependencies << object
        end

        def <=>(other)
          @object.path <=> other.path
        end

        def hash
          @object.path.hash
        end

      end


      class NodeFactory

        def initialize
          @dependencies = {}
        end

        def node(yard_object)
          item = @dependencies[yard_object.path]
          return item if item
          item = Node.new(yard_object)
          @dependencies[yard_object.path] = item
          item
        end

      end


      def resolve_dependencies(yard_objects)
        factory = NodeFactory.new
        dependencies = SortedSet.new

        yard_objects.each { |yard_object|
          object = factory.node(yard_object)

          if yard_object.type == :class && yard_object.superclass.name != :Object
            object << factory.node(yard_object.superclass)
          end

          unless yard_object.namespace.path.empty?
            object << factory.node(yard_object.namespace)
          end

          dependencies << object
        }
        dependencies
      end

      def print_dependencies(object, indent = 0)
        indent_space = '  ' * indent
        puts "#{indent_space}#{object.path}"
        object.dependencies.each { |dependency|
          print_dependencies(dependency, indent + 1)
        }
      end

      def sketchup_stubs_path
        File.join('sketchup-api-stubs', 'stubs')
      end

      def stub_filename(object)
        basename = object.path.gsub('::', '/')
        basename = '_top_level' if basename.empty?
        File.join(sketchup_stubs_path, "#{basename}.rb")
      end

      def recurse_require(object, required, out, indent = 0)
        return if required.include?(object.path)
        # If a class inherit from a core Ruby class we might get a Proxy code
        # object here. We don't generate stubs for those and thus we don't want
        # to create a require for them either.
        return if object.object.is_a?(YARD::CodeObjects::Proxy)
        # First require the dependencies.
        object.dependencies.each { |dependency|
          recurse_require(dependency, required, out, indent + 1)
        }
        # Then we're good to require this object.
        require_path = stub_filename(object)
        # indent_space = '  ' * indent
        # out.puts "#{indent_space}require '#{require_path}'"
        out.puts "require '#{require_path}'"
        required << object.path
      end

    end
  end
end
