require 'yaml'

require 'debug_helper/version'

class DebugHelper

  attr_accessor \
      :obj,
      :name,
      :object_ids

  def self.show(obj, name = obj.class)
    debug_helper = DebugHelper.new(obj, name)
    x = debug_helper.send(:_show, obj, name, info = {})
    puts x.to_yaml
  end

  private

  def initialize(obj, name)
    self.obj = obj
    self.name = name
    self.object_ids = []
  end

  def _show(obj, name, info)
    if object_ids.include?(obj.object_id)
      s = show_object(obj, name, info)
    else
      object_ids.push(obj.object_id)
      s = case
            when obj.kind_of?(Array)
              show_array(obj, name, info)
            when obj.kind_of?(Hash)
              show_hash(obj, name, info)
            when obj.kind_of?(String)
              show_string(obj, name, info)
            when obj.kind_of?(Struct)
              show_struct(obj, name, info)
            when obj.kind_of?(Symbol)
              show_symbol(obj, name, info)
            # when obj.kind_of?(Range)
            # when obj.kind_of?(Set)
            else
              show_object(obj, name, info)
          end
    end
    object_ids.pop
    s
  end

  def show_array(obj, name, info)
    content = {}
    obj.each_with_index do |item, i|
      content.store("Element #{i}", _show(item, nil, {}))
    end
    attrs = {
        :name => name,
        :size => obj.size,
    }
    _show_item(obj.class.name, content, attrs, info)
  end

  def show_hash(obj, name, info)
    content = {}
    obj.each_with_index do |pair, i|
      key, value = *pair
      pair = {'Key' => _show(key, nil, {}), 'Value' => _show(value, nil, {})}
      content.store("Pair #{i}", pair)
    end
    attrs = {
        :size => obj.size,
        :default => obj.default,
        :default_proc => obj.default_proc,
        :name => name,
    }
    _show_item(obj.class.name, content, attrs, info)
  end

  def show_object(obj, name, info)
    name_info = name.nil? ? '' : " (name='#{name}')"
    "#{obj.class.name}#{name_info} #{obj}"
  end

  def show_string(obj, name, info)
    attrs = {
        :name => name,
        :size => obj.size,
        :encoding => obj.encoding,
        :ascii_only => obj.ascii_only?,
        :bytesize => obj.bytesize,
    }
    _show_item(obj.class.name, [obj], attrs, info)
  end

  def show_struct(obj, name, info)
    content = {}
    i = 0
    obj.each_pair do |member|
      member_name, value = *member
      pair = {'Name' => member_name, 'Value' => _show(value, nil, {})}
      content.store("Member #{i}", pair)
      i += 1
    end
    attrs = {
        :name => name,
        :size => obj.size,
    }
    _show_item(obj.class.name, content, attrs, info)
  end

  def show_symbol(obj, name, info)
    attrs = {
        :name => name,
        :size => obj.size,
        :encoding => obj.encoding,
    }
    _show_item(obj.class.name, obj, attrs, info)
  end

  def _show_item(class_name, content, attrs, info)
    name = attrs[:name]
    unless name.nil?
      attrs[:name] = "'#{name}'"
    end
    label = label(class_name, attrs)
    info.store(label, content)
    info
  end

  def label(class_name, attrs)
    a = []
    attrs.each_pair do |key, value|
      a.push("#{key}=#{value}") unless value.nil?
    end
    attrs_s = a.join(' ')
    "#{class_name} (#{attrs_s})"
  end

  # def respond_to!(obj, method)
  #   unless obj.respond_to?(method)
  #     message = "Instance of #{obj.class.name} does not respond to :#{method}"
  #     raise ArgumentError.new(message)
  #   end
  # end
  #
  # def kind_of!(obj, klass)
  #   unless obj.kind_of?(klass)
  #     message = "Instance of #{obj.class.name} is not a kind of #{klass}"
  #     raise ArgumentError.new(message)
  #   end
  # end

end