require 'ostruct'
require 'set'
require 'yaml'

require_relative 'debug_helper/version'

require_relative 'debug_helper/handler'

  require_relative 'debug_helper/each_with_index_handler'
    require_relative 'debug_helper/array_handler'
    require_relative 'debug_helper/set_handler'

  require_relative 'debug_helper/each_pair_handler'
    require_relative 'debug_helper/hash_handler'
    require_relative 'debug_helper/struct_handler'

  require_relative 'debug_helper/dir_handler'
  require_relative 'debug_helper/exception_handler'
  require_relative 'debug_helper/file_handler'
  require_relative 'debug_helper/generic_handler'
  require_relative 'debug_helper/io_handler'
  require_relative 'debug_helper/match_data_handler'
  require_relative 'debug_helper/object_handler'
  require_relative 'debug_helper/open_struct_handler'
  require_relative 'debug_helper/regexp_handler'
  require_relative 'debug_helper/string_handler'
  require_relative 'debug_helper/symbol_handler'
  require_relative 'debug_helper/range_handler'

class DebugHelper

  module Putd

    def putd(obj, message, options = {})
      DebugHelper.show(obj, message, options)
    end

  end

  attr_accessor \
      :obj,
      :message,
      :object_ids,
      :depth

  def self.show(obj, message = obj.class, options = {})
    debug_helper = DebugHelper.new(obj, message, options)
    s = debug_helper.show(obj, message, info = {})
    puts s.to_yaml
  end

  def initialize(obj, message, options)
    self.obj = obj
    self.message = message
    self.depth = options[:depth] || 3
    self.object_ids = []
  end

  def object_seen?(obj)
    object_ids.include?(obj.object_id)
  end

  def depth_reached?
    depth == object_ids.size
  end

  def show(obj, message, info)
    handler = nil
    if object_seen?(obj) || depth_reached?
      handler = GenericHandler.new(self, obj, message, info)
      s = handler.show
    else
      object_ids.push(obj.object_id)
      begin
        # If there's a handler for the class, use it.
        # Note that the class may be a custom class, not defined here,
        # but instead defined outside this project.
        # So if the user of this library has defined DebugHelper::FooHandler
        # and the object is a Foo, that handler will be selected and called.
        handler_class_name = "DebugHelper::#{obj.class.name}Handler"
        handler_class = Object.const_get(handler_class_name)
        handler = handler_class.new(method(__method__), obj, message, info)
      rescue
        # If there's not a handler for the class, try using :kind_of?.
        [
            Array,
            Dir,
            Exception,
            File,
            IO,
            Hash,
            OpenStruct,
            Range,
            Set,
            String,
            Struct,
            # There's no method Symbol.new, so cannot instantiate a subclass.
            # Symbol,
        ].each do |klass|
          if obj.kind_of?(klass)
            handler_class_name = "DebugHelper::#{klass.name}Handler"
            handler_class = Object.const_get(handler_class_name)
            handler = handler_class.new(method(__method__), obj, message, info)
            break
          end
        end
      end
      if handler.nil?
        if obj.instance_of?(Object)
          handler_class= ObjectHandler
        else
          handler_class = GenericHandler
        end
        handler = handler_class.new(self, obj, message, info)
      end
      s = handler.show
      object_ids.pop
    end
    s
  end

end