# frozen_string_literal: true

require 'optparse'
require 'rainbow'
require_relative '../version'
require_relative 'status'
require_relative '../detector_repository'
require_relative '../documentation_link'

module Reek
  module CLI
    #
    # Parses the command line
    #
    # See {file:docs/Command-Line-Options.md} for details.
    #
    # @quality :reek:TooManyInstanceVariables { max_instance_variables: 13 }
    # @quality :reek:TooManyMethods { max_methods: 18 }
    # @quality :reek:Attribute { enabled: false }
    #
    class Options
      attr_reader :argv, :parser, :smells_to_detect
      attr_accessor :colored,
                    :config_file,
                    :location_format,
                    :progress_format,
                    :report_format,
                    :show_empty,
                    :show_links,
                    :sorting,
                    :stdin_filename,
                    :success_exit_code,
                    :failure_exit_code,
                    :generate_todo_list,
                    :force_exclusion,
                    :show_configuration_path

      def initialize(argv = [])
        @argv                    = argv
        @parser                  = OptionParser.new
        @report_format           = :text
        @location_format         = :numbers
        @progress_format         = tty_output? ? :dots : :quiet
        @show_links              = true
        @smells_to_detect        = []
        @colored                 = tty_output?
        @success_exit_code       = Status::DEFAULT_SUCCESS_EXIT_CODE
        @failure_exit_code       = Status::DEFAULT_FAILURE_EXIT_CODE
        @generate_todo_list      = false
        @force_exclusion         = false
        @show_configuration_path = false

        set_up_parser
      end

      def parse
        parser.parse!(argv)
        Rainbow.enabled = colored
        self
      end

      def force_exclusion?
        @force_exclusion
      end

      private

      # TTY output generally means the output will not undergo further
      # processing by a machine, but will be viewed by a human. This means
      # features like coloring can be safely enabled by default.
      #
      # @quality :reek:UtilityFunction
      def tty_output?
        $stdout.tty?
      end

      # @quality :reek:TooManyStatements { max_statements: 7 }
      def set_up_parser
        set_banner
        set_configuration_options
        set_generate_todo_list_options
        set_alternative_formatter_options
        set_report_formatting_options
        set_exit_codes
        set_utility_options
      end

      def set_banner
        program_name = parser.program_name
        parser.banner = <<-BANNER.gsub(/^ +/, '')
          Usage: #{program_name} [options] [files]

          Examples:

          #{program_name} lib/*.rb
          #{program_name} -s lib
          cat my_class.rb | #{program_name}

          See https://github.com/troessner/reek for detailed help.

        BANNER
      end

      # @quality :reek:TooManyStatements { max_statements: 7 }
      def set_configuration_options
        parser.separator 'Configuration:'
        parser.on('-c', '--config FILE', 'Read configuration options from FILE') do |file|
          self.config_file = Pathname.new(file)
        end
        parser.on('--smell SMELL',
                  'Only look for a specific smell.',
                  'Call it like this: reek --smell MissingSafeMethod source.rb',
                  "Check out #{DocumentationLink.build('Code Smells')} " \
                  'for a list of smells') do |smell|
          smells_to_detect << smell
        end
        parser.on('--stdin-filename FILE',
                  'When passing code in via pipe, assume this filename when ' \
                  'checking file or directory rules in the config.') do |file|
          self.stdin_filename = file
        end
      end

      def set_generate_todo_list_options
        parser.separator "\nGenerate a todo list:"
        parser.on('-t', '--todo', 'Generate a todo list') do
          self.generate_todo_list = true
        end
      end

      def set_alternative_formatter_options
        parser.separator "\nReport format:"
        parser.on(
          '-f', '--format FORMAT', [:html, :text, :yaml, :json, :xml, :github],
          'Report smells in the given format:',
          '  html', '  text (default)', '  yaml', '  json', '  xml', '  github') do |opt|
          self.report_format = opt
        end
      end

      # @quality :reek:TooManyStatements { max_statements: 7 }
      def set_report_formatting_options
        parser.separator "\nText format options:"
        set_up_color_option
        set_up_verbosity_options
        set_up_location_formatting_options
        set_up_progress_formatting_options
        set_up_sorting_option
        set_up_force_exclusion_option
      end

      def set_up_color_option
        parser.on('--[no-]color', 'Use colors for the output (default: true)') do |opt|
          self.colored = opt
        end
      end

      # @quality :reek:TooManyStatements { max_statements: 6 }
      def set_up_verbosity_options
        parser.on('-V', '--[no-]empty-headings',
                  'Show headings for smell-free source files (default: false)') do |show_empty|
          self.show_empty = show_empty
        end
        parser.on('-U', '--[no-]documentation',
                  'Show link to related documentation page for each smell (default: true)') do |show_links|
          self.show_links = show_links
        end
        parser.on(nil, '--[no-]show-configuration-path',
                  'Show which configuration file Reek is using (default: false)') do |show_configuration_path|
          self.show_configuration_path = show_configuration_path
        end
      end

      def set_up_location_formatting_options
        parser.on('-n', '--[no-]line-numbers',
                  'Show line numbers in the output (default: true)') do |show_numbers|
          self.location_format = show_numbers ? :numbers : :plain
        end
        parser.on('-s', '--single-line',
                  'Show location in editor-compatible single-line-per-smell format') do
          self.location_format = :single_line
        end
      end

      def set_up_progress_formatting_options
        parser.on('-P', '--[no-]progress',
                  'Show progress of each source as it is examined (default: true)') do |show_progress|
          self.progress_format = show_progress ? :dots : :quiet
        end
      end

      def set_up_sorting_option
        parser.on('--sort-by SORTING', [:smelliness, :none],
                  'Sort reported files by the given criterium:',
                  '  smelliness ("smelliest" files first)',
                  '  none (default - output in processing order)') do |sorting|
          self.sorting = sorting
        end
      end

      def set_up_force_exclusion_option
        parser.on('--force-exclusion',
                  'Force excluding files specified in the configuration `exclude_paths`',
                  '  even if they are explicitly passed as arguments') do |force_exclusion|
          self.force_exclusion = force_exclusion
        end
      end

      # @quality :reek:DuplicateMethodCall { max_calls: 2 }
      def set_exit_codes
        parser.separator "\nExit codes:"
        parser.on('--success-exit-code CODE',
                  'The exit code when no smells are found ' \
                  "(default: #{Status::DEFAULT_SUCCESS_EXIT_CODE})") do |option|
          self.success_exit_code = Integer(option)
        end
        parser.on('--failure-exit-code CODE',
                  'The exit code when smells are found ' \
                  "(default: #{Status::DEFAULT_FAILURE_EXIT_CODE})") do |option|
          self.failure_exit_code = Integer(option)
        end
      end

      # @quality :reek:TooManyStatements { max_statements: 12 }
      def set_utility_options
        parser.separator "\nUtility options:"
        parser.on_tail('-h', '--help', 'Show this message') do
          puts parser
          exit
        end
        parser.on_tail('-l', '--list', 'List all available smell detectors') do
          puts "All available smell detectors:\n\n"
          puts DetectorRepository.available_detector_names
          puts "\nCheck out #{DocumentationLink.build('Code Smells')} " \
               'for a details on each detector'
          exit
        end
        parser.on_tail('-v', '--version', 'Show version') do
          puts "#{parser.program_name} #{Reek::Version::STRING}\n"
          exit
        end
      end
    end
  end
end
