# frozen_string_literal: true
module YARD
  module Handlers
    module C
      class Base < Handlers::Base
        include YARD::Parser::C
        include HandlerMethods

        # @return [Boolean] whether the handler handles this statement
        def self.handles?(statement, processor)
          processor.globals.cruby_processed_files ||= {}
          processor.globals.cruby_processed_files[processor.file] = true

          src = statement.respond_to?(:declaration) ?
            statement.declaration : statement.source

          handlers.any? do |a_handler|
            statement_class >= statement.class &&
              case a_handler
              when String
                src == a_handler
              when Regexp
                src =~ a_handler
              end
          end
        end

        def self.statement_class(type = nil)
          if type
            @statement_class = type
          else
            (defined?(@statement_class) && @statement_class) || Statement
          end
        end

        # @group Registering objects

        def register_docstring(object, docstring = nil, stmt = nil)
          super(object, docstring, stmt) if docstring
        end

        def register_file_info(object, file = nil, line = nil, comments = nil)
          super(object, file, line, comments) if file
        end

        def register_source(object, source = nil, type = nil)
          super(object, source, type) if source
        end

        def register_visibility(object, visibility = nil)
          super(object, visibility) if visibility
        end

        # @group Looking up Symbol and Var Values

        def symbols
          globals.cruby_symbols ||= {}
        end

        def override_comments
          globals.cruby_override_comments ||= []
        end

        def namespace_for_variable(var)
          return namespaces[var] if namespaces[var]

          # The global variables for Ruby's core error classes does not
          # represent their Ruby name. So we need to look up these names.
          name = ERROR_CLASS_NAMES[var]
          return P(name) if name

          # Otherwise the name is inferred from the C variable name.
          var = remove_var_prefix(var)
          var.empty? ? nil : P(var)
        end

        def ensure_variable_defined!(var, max_retries = 1)
          retries = 0
          object = nil

          loop do
            object = namespace_for_variable(var)
            break unless object.is_a?(Proxy)

            raise NamespaceMissingError, object if retries > max_retries
            log.debug "Missing namespace variable #{var} in file `#{parser.file}', moving it to the back of the line."
            parser.parse_remaining_files
            retries += 1
          end

          object
        end

        def namespaces
          globals.cruby_namespaces ||= {}
        end

        def processed_files
          globals.cruby_processed_files ||= {}
        end

        # @group Parsing an Inner Block

        def parse_block(opts = {})
          return if !statement.block || statement.block.empty?
          push_state(opts) do
            parser.process(statement.block)
          end
        end

        # @group Processing other files

        def process_file(file, object)
          file = File.cleanpath(file)
          return if processed_files[file]
          processed_files[file] = file
          begin
            log.debug "Processing embedded call to C source #{file}..."
            globals.ordered_parser.files.delete(file) if globals.ordered_parser
            parser.process(Parser::C::CParser.new(File.read(file), file).parse)
          rescue Errno::ENOENT
            log.warn "Missing source file `#{file}' when parsing #{object}"
          end
        end

        # @endgroup

        private

        # Generated by update_error_map.rb (Copy+past results)
        ERROR_CLASS_NAMES = {
          'rb_eArgError' => 'ArgumentError',
          'rb_eEncodingError' => 'EncodingError',
          'rb_eException' => 'Exception',
          'rb_eFatal' => 'fatal',
          'rb_eFrozenError' => 'FrozenError',
          'rb_eIndexError' => 'IndexError',
          'rb_eInterrupt' => 'Interrupt',
          'rb_eKeyError' => 'KeyError',
          'rb_eLoadError' => 'LoadError',
          'rb_eNameError' => 'NameError',
          'rb_eNoMatchingPatternError' => 'NoMatchingPatternError',
          'rb_eNoMemError' => 'NoMemoryError',
          'rb_eNoMethodError' => 'NoMethodError',
          'rb_eNotImpError' => 'NotImplementedError',
          'rb_eRangeError' => 'RangeError',
          'rb_eRuntimeError' => 'RuntimeError',
          'rb_eScriptError' => 'ScriptError',
          'rb_eSecurityError' => 'SecurityError',
          'rb_eSignal' => 'SignalException',
          'rb_eStandardError' => 'StandardError',
          'rb_eSyntaxError' => 'SyntaxError',
          'rb_eSystemCallError' => 'SystemCallError',
          'rb_eSystemExit' => 'SystemExit',
          'rb_eTypeError' => 'TypeError',
        }

        def remove_var_prefix(var)
          var.gsub(/^rb_[mc]|^[a-z_]+/, '')
        end
      end
    end
  end
end
