require 'web_tools/support/ruby'

module WebTools
  module Support
    # The CodeBrowser is a ViewModel for MagLev code browsing.  It keeps the
    # entire state of the UI and returns structured data for use by a UI.
    # For the first pass, we do not cache anything.
    class CodeBrowser
      def self.class_and_module_list
        { 'modules' => Ruby.class_and_module_names }
      end

      def select_module(module_name)
        mod = Ruby.find_in_namespace(module_name)
        methods = mod.instance_methods(false) +
          mod.protected_instance_methods(false) +
          mod.private_instance_methods(false)
        {
          :ancestors          => mod.ancestors.reverse,
          :constants          => mod.constants.sort,
          :instance_methods   => methods.uniq.sort,
          :module_methods     => Ruby.module_fns_for(mod),
          :selected_module    => module_name,
        }
      end

      def select_constant(module_name, const_name)
        parent = Ruby.find_in_namespace module_name
        {
          :const_value       => ObjectInfo.for(parent.const_get(const_name)),
          :selected_constant => const_name,
          :selected_module   => module_name,
        }
      end

      def select_method(module_name, method_name, is_instance_method)
        mod = Ruby.find_in_namespace(module_name)
        src, file, line = mod.method_source(method_name, is_instance_method)
        {
          :is_instance_method => is_instance_method,
          :method_line_number => line,
          :method_source      => src,
          :method_source_file => file,
          :module_name        => module_name,
          :selected_method    => method_name,
        }
      end

      def object_info(object_id)
        ObjectInfo.for(object_id)
      end
    end

    class ObjectInfo
      # Return a new ObjectInfo instance for the object with the given object
      # id.
      def self.for_id(object_id)
        new(ObjectSpace._id2ref(object_id))
      end

      # Return a new ObjectInfo instance for the given object.
      def self.for(object)
        new(object)
      end

      attr_reader :info

      def initialize(obj)
        @info = { }
        @info[:object_id]          = obj.object_id
        @info[:class]              = obj.class.name
        @info[:inspect]            = obj.inspect
        @info[:instance_variables] = []
        @info[:enumerated]         = []
        @info[:enumerated_size]    = obj.respond_to?(:size) ? obj.size : nil

        obj.instance_variables.each do |iv|
          val = obj.instance_variable_get(iv)
          @info[:instance_variables] << [iv, val.inspect, val.object_id]
        end

        limit = 10
        case obj
        when Enumerable, Array
          obj.each_with_index do |o,i|
            if i > limit
              @info[:enumerated] << '...'
              break
            else
              @info[:enumerated] << o.inspect
            end
          end

        when Hash
          obj.each_with_index do |pair, idx|
            if idx > limit
              @info[:enumerated] << ['', '...']
              break
            end
            @info[:enumerated] << [pair[0].to_s, pair[1].inspect]
          end
        end

      end

      def to_json(*args)
        @info.to_json
      end
    end
  end
end
