# frozen_string_literal: true

# Copyright (c) 2010-2016 Michael Dvorkin and contributors
#
# AmazingPrint is freely distributable under the terms of MIT license.
# See LICENSE file or http://www.opensource.org/licenses/mit-license.php
#------------------------------------------------------------------------------

# rubocop:disable Metrics/ClassLength

require_relative 'indentator'

module AmazingPrint
  class Inspector
    attr_accessor :options, :indentator

    AP = :__amazing_print__

    ##
    # Unload the cached dotfile and load it again.
    #
    def self.reload_dotfile
      @@dotfile = nil
      new.send :load_dotfile
      true
    end

    def initialize(options = {})
      @options = {
        indent: 4, # Number of spaces for indenting.
        index: true, # Display array indices.
        html: false, # Use ANSI color codes rather than HTML.
        multiline: true, # Display in multiple lines.
        plain: false, # Use colors.
        raw: false, # Do not recursively format instance variables.
        sort_keys: false,  # Do not sort hash keys.
        sort_vars: true,   # Sort instance variables.
        limit: false, # Limit arrays & hashes. Accepts bool or int.
        ruby19_syntax: false, # Use Ruby 1.9 hash syntax in output.
        class_name: :class, # Method used to get Instance class name.
        object_id: true, # Show object_id.
        color: {
          args: :whiteish,
          array: :white,
          bigdecimal: :blue,
          class: :yellow,
          date: :greenish,
          falseclass: :red,
          fixnum: :blue,
          integer: :blue,
          float: :blue,
          hash: :whiteish,
          keyword: :cyan,
          method: :purpleish,
          nilclass: :red,
          rational: :blue,
          string: :yellowish,
          struct: :whiteish,
          symbol: :cyanish,
          time: :greenish,
          trueclass: :green,
          variable: :cyanish
        }
      }

      # Merge custom defaults and let explicit options parameter override them.
      merge_custom_defaults!
      merge_options!(options)

      @formatter = AmazingPrint::Formatter.new(self)
      @indentator = AmazingPrint::Indentator.new(@options[:indent].abs)
      Thread.current[AP] ||= []
    end

    def current_indentation
      indentator.indentation
    end

    def increase_indentation(&blk)
      indentator.indent(&blk)
    end

    # Dispatcher that detects data nesting and invokes object-aware formatter.
    #---------------------------------------------------------------------------
    def awesome(object)
      if Thread.current[AP].include?(object.object_id)
        nested(object)
      else
        begin
          Thread.current[AP] << object.object_id
          unnested(object)
        ensure
          Thread.current[AP].pop
        end
      end
    end

    # Return true if we are to colorize the output.
    #---------------------------------------------------------------------------
    def colorize?
      AmazingPrint.force_colors ||= false
      AmazingPrint.force_colors || (
        if defined? @colorize_stdout
          @colorize_stdout
        else
          @colorize_stdout = $stdout.tty? && (
            (
              ENV['TERM'] &&
              ENV['TERM'] != 'dumb'
            ) ||
            ENV['ANSICON']
          )
        end
      )
    end

    private

    # Format nested data, for example:
    #   arr = [1, 2]; arr << arr
    #   => [1,2, [...]]
    #   hash = { :a => 1 }; hash[:b] = hash
    #   => { :a => 1, :b => {...} }
    #---------------------------------------------------------------------------
    def nested(object)
      case printable(object)
      when :array  then @formatter.colorize('[...]', :array)
      when :hash   then @formatter.colorize('{...}', :hash)
      when :struct then @formatter.colorize('{...}', :struct)
      else @formatter.colorize("...#{object.class}...", :class)
      end
    end

    #---------------------------------------------------------------------------
    def unnested(object)
      @formatter.format(object, printable(object))
    end

    # Turn class name into symbol, ex: Hello::World => :hello_world. Classes
    # that inherit from Array, Hash, File, Dir, and Struct are treated as the
    # base class.
    #---------------------------------------------------------------------------
    def printable(object)
      case object
      when Array  then :array
      when Hash   then :hash
      when File   then :file
      when Dir    then :dir
      when Struct then :struct
      else object.class.to_s.gsub(/:+/, '_').downcase.to_sym
      end
    end

    # Update @options by first merging the :color hash and then the remaining
    # keys.
    #---------------------------------------------------------------------------
    def merge_options!(options = {})
      @options[:color].merge!(options.delete(:color) || {})
      @options.merge!(options)
    end

    def find_dotfile
      xdg_config_home = File.expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config'))
      xdg_config_path = File.join(xdg_config_home, 'aprc') # ${XDG_CONFIG_HOME}/aprc

      return xdg_config_path if File.exist?(xdg_config_path)

      # default to ~/.aprc
      File.join(Dir.home, '.aprc')
    end

    # This method needs to be mocked during testing so that it always loads
    # predictable values
    #---------------------------------------------------------------------------
    def load_dotfile
      return if @@dotfile # Load the dotfile only once.

      dotfile = find_dotfile
      load dotfile if dotfile_readable?(dotfile)
    end

    def dotfile_readable?(dotfile)
      @@dotfile_readable = File.readable?(@@dotfile = dotfile) if @@dotfile_readable.nil? || @@dotfile != dotfile
      @@dotfile_readable
    end
    @@dotfile_readable = @@dotfile = nil

    # Load ~/.aprc file with custom defaults that override default options.
    #---------------------------------------------------------------------------
    def merge_custom_defaults!
      load_dotfile
      merge_options!(AmazingPrint.defaults) if AmazingPrint.defaults.is_a?(Hash)
    rescue StandardError => e
      warn "Could not load '.aprc' from ENV['HOME']: #{e}"
    end
  end
end

# rubocop:enable Metrics/ClassLength
