# Copyright (c) 2023 M.J.N. Corino, The Netherlands
#
# This software is released under the MIT license.

###
# wxRuby3 buildtools configuration
###

require 'rbconfig'
require 'fileutils'
require 'json'
require 'open3'
require 'monitor'
require 'plat4m'

module FileUtils
  # add convenience methods
  def rmdir_if(list, **kwargs)
    list = fu_list(list).select { |path| File.exist?(path) }
    rmdir(list, **kwargs) unless list.empty?
  end
  def rm_if(list, **kwargs)
    list = fu_list(list).select { |path| File.exist?(path) }
    rm_f(list, **kwargs) unless list.empty?
  end
end

module WXRuby3
  ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))

  if defined? ::RbConfig
    RB_CONFIG = ::RbConfig::CONFIG
  else
    RB_CONFIG = ::Config::CONFIG
  end unless defined? RB_CONFIG

  CFG_KEYS = %w[prefix
                bindir
                libdir
                datadir
                mandir
                sysconfdir
                localstatedir
                libruby
                librubyver
                librubyverarch
                siteruby
                siterubyver
                siterubyverarch
                rbdir
                sodir]

  RB_DEFAULTS = %w[bindir
                   libdir
                   datadir
                   mandir
                   sysconfdir
                   localstatedir]

  CONFIG = {
    'libruby' => File.join(RB_CONFIG['libdir'], 'ruby'),
    'librubyver' => RB_CONFIG['rubylibdir'],
    'librubyverarch' => RB_CONFIG['archdir'],
    'siteruby' => RB_CONFIG['sitedir'],
    'siterubyver' => RB_CONFIG['sitelibdir'],
    'siterubyverarch' => RB_CONFIG['sitearchdir'],
    'rbdir' => '$siterubyver',
    'sodir' => '$siterubyverarch',
  }

  CFG_KEYS.concat(%w{wxwin wxxml wxwininstdir with-wxwin with-debug swig doxygen git})
  WXW_SYS_KEY = 'with-system-wxwin'
  CONFIG.merge!({
                  'wxwin' => ENV['WXWIN'] || '',
                  'wxxml' => ENV['WXXML'] || '',
                  'wxwininstdir' => '',
                  'with-wxwin' => !!ENV['WITH_WXWIN'],
                  'with-debug' => ((ENV['WXRUBY_DEBUG'] || '') == '1'),
                  'swig' => ENV['WXRUBY_SWIG'] || 'swig',
                  'doxygen' => ENV['WXRUBY_DOXYGEN'] || 'doxygen',
                  'git' => ENV['WXRUBY_GIT'] || 'git'
                })
  CONFIG['autoinstall'] = (ENV['WXRUBY_AUTOINSTALL'] != '0') if ENV['WXRUBY_AUTOINSTALL']
  BUILD_CFG = '.wxconfig'

  # Ruby 2.7 is the minimum version for wxRuby3
  __rb_ver = RUBY_VERSION.split('.').collect {|v| v.to_i}
  if (__rb_major = __rb_ver.shift) < 2 || (__rb_major == 2 && __rb_ver.shift < 7)
    $stderr.puts 'ERROR: wxRuby3 requires Ruby >= 2.7.0!'
    exit(1)
  end

  # Pure-ruby lib files
  ALL_RUBY_LIB_FILES = FileList[ 'lib/**/*.rb' ]

  # The version file
  VERSION_FILE = File.join(ROOT,'lib', 'wx', 'version.rb')

  # Setting the version via an environment variable
  if ENV['WXRUBY_VERSION']
    WXRUBY_VERSION = ENV['WXRUBY_VERSION']
    File.open(VERSION_FILE, 'w') do | version_file |
      version_file.puts "module Wx"
      version_file.puts "  WXRUBY_VERSION    = '#{WXRUBY_VERSION}#{ENV['WXRUBY_RELEASE_TYPE'] || ''}'"
      version_file.puts "end"
    end
    # Try loading the existing version file
  elsif File.exist?(VERSION_FILE)
    require VERSION_FILE
    WXRUBY_VERSION = Wx::WXRUBY_VERSION
    # Leave version undefined
  else
    WXRUBY_VERSION = ''
  end

  WXWIN_MINIMUM = '3.2.0'

  module Config

    def self.command_to_s(*cmd)
      txt = if ::Hash === cmd.first
              cmd = cmd.dup
              env = cmd.shift
              env.collect { |k, v| "#{k}=#{v}" }.join(' ') << ' '
            else
              ''
            end
      txt << cmd.join(' ')
    end

    def run_silent?
      !!ENV['WXRUBY_RUN_SILENT']
    end

    def silent_log_name
      ENV['WXRUBY_RUN_SILENT'] || 'silent_run.log'
    end

    def log_progress(msg)
      run_silent? ? silent_runner.log(msg) : $stdout.puts(msg)
    end

    class SilentRunner < Monitor

      PROGRESS_CH = '.|/-\\|/-\\|'

      def initialize
        super
        @cout = 0
        @incremental = false
      end

      def incremental(f=true)
        synchronize do
          @cout = 0
          @incremental = !!f
        end
      end

      def run(*cmd, **kwargs)
        synchronize do
          @cout = 0 unless @incremental
        end
        output = nil
        verbose = kwargs.delete(:verbose)
        capture = kwargs.delete(:capture)
        if (Config.instance.verbose? && verbose != false) || !capture
          txt = Config.command_to_s(*cmd)
          if Config.instance.verbose? && verbose != false
            $stdout.puts txt
          end
          if !capture
            silent_log { |f| f.puts txt }
          end
        end
        if capture
          if capture == :out || capture == :no_err
            kwargs[:err] = (Config.instance.windows? ? 'NULL' : '/dev/null') if capture == :no_err
            Open3.popen2(*cmd, **kwargs) do |_ins, os, tw|
              output = silent_runner(os)
              tw.value
            end
          else
            Open3.popen2e(*cmd, **kwargs) do |_ins, eos, tw|
              output = silent_runner(eos)
              tw.value
            end
          end
          output.join
        else
          rc = silent_log do |fout|
            Open3.popen2e(*cmd, **kwargs) do |_ins, eos, tw|
              silent_runner(eos, fout)
              v = tw.value.exitstatus
              fout.puts "-> Exit code: #{v}"
              v
            end
          end
          rc
        end
      end

      def log(msg)
        silent_log { |f| f.puts(msg) }
      end

      private

      def silent_runner(os, output=[])
        synchronize do
          if @incremental
            @cout += 1
            $stdout.print "#{PROGRESS_CH[@cout%10]}\b"
            $stdout.flush
          end
        end
        os.each do |ln|
          synchronize do
            unless @incremental
              @cout += 1
              $stdout.print "#{PROGRESS_CH[@cout%10]}\b"
              $stdout.flush
            end
            output << ln
          end
        end
        output
      end

      def silent_log(&block)
        File.open(Config.instance.silent_log_name, 'a') do |fout|
          block.call(fout)
        end
      end

    end

    def silent_runner
      @silent_runner ||= SilentRunner.new
    end
    private :silent_runner

    def do_silent_run(*cmd, **kwargs)
      silent_runner.run(*cmd, **kwargs)
    end
    private :do_silent_run

    def do_silent_run_step(*cmd, **kwargs)
      silent_runner.run_one(*cmd, **kwargs)
    end
    private :do_silent_run_step

    def set_silent_run_incremental
      silent_runner.incremental
    end

    def set_silent_run_batched
      silent_runner.incremental(false)
    end

    def do_run(*cmd, capture: nil)
      output = nil
      if run_silent?
        output = do_silent_run(exec_env, *cmd, capture: capture)
        unless capture
          fail "Command failed with status (#{rc}): #{Config.command_to_s(*cmd)}" unless output == 0
        end
      else
        if capture
          env_bup = exec_env.keys.inject({}) do |h, ev|
            h[ev] = ENV[ev] ? ENV[ev].dup : nil
            h
          end
          case capture
          when :out
            # default
          when :no_err
            # redirect stderr to null sink
            cmd << '2> ' << (windows? ? 'NULL' : '/dev/null')
          when :err, :all
            cmd << '2>&1'
          end
          begin
            # setup ENV for child execution
            ENV.update(Config.instance.exec_env)
            output = `#{cmd.join(' ')}`
          ensure
            # restore ENV
            env_bup.each_pair do |k,v|
              if v
                ENV[k] = v
              else
                ENV.delete(k)
              end
            end
          end
        else
          Rake.sh(exec_env, *cmd, verbose: verbose?)
        end
      end
      output
    end
    private :do_run

    def make_ruby_cmd(*cmd, verbose: true)
      [
        FileUtils::RUBY,
        '-I', rb_lib_path,
        (verbose && verbose? ? '-v' : nil),
        *cmd.flatten
      ].compact
    end
    private :make_ruby_cmd

    def execute(*cmd)
      sh(exec_env.merge({'RUBYLIB'=>rb_lib_path}), cmd.flatten.join(' '), fail_on_error: true)
    end

    def run(*cmd, capture: nil, verbose: true)
      do_run(*make_ruby_cmd(cmd, verbose: verbose), capture: capture)
    end

    def debug_command(*args)
      raise "Do not know how to debug for platform #{platform}"
    end

    def debug(*args, **options)
      args.unshift("-I#{File.join(Config.wxruby_root, 'lib')}")
      Rake.sh(exec_env, debug_command(*args), **options)
    end

    def respawn_rake(argv = ARGV)
      Kernel.exec($0, *argv)
    end

    def expand(cmd)
      `#{cmd}`
    end

    def sh(*cmd, fail_on_error: false, **kwargs)
      if run_silent?
        rc = do_silent_run(*cmd, **kwargs)
        fail "Command failed with status (#{rc}): #{Config.command_to_s(*cmd)}" if fail_on_error && rc != 0
        rc == 0
      elsif fail_on_error
        Rake.sh(*cmd, **kwargs)
      else
        Rake.sh(*cmd, **kwargs) { |ok,_| !!ok }
      end
    end
    alias :bash :sh

    def test(*tests, **options)
      errors = 0
      excludes = (ENV['WXRUBY_TEST_EXCLUDE'] || '').split(':')
      tests = Dir.glob(File.join(Config.instance.test_dir, '*.rb')) if tests.empty?
      tests.each do |test|
        unless excludes.include?(File.basename(test, '.*'))
          unless File.exist?(test)
            test = File.join(Config.instance.test_dir, test)
            test = Dir.glob(test+'.rb').shift || test unless File.exist?(test)
          end
          Rake.sh(Config.instance.exec_env.merge({'RUBYLIB'=>rb_lib_path}), FileUtils::RUBY, test) { |ok,status| errors += 1 unless ok }
        end
      end
      fail "ERRORS: ##{errors} test scripts failed." if errors>0
    end

    def irb(**options)
      irb_cmd = File.join(File.dirname(FileUtils::RUBY), 'irb')
      Rake.sh(Config.instance.exec_env, *make_ruby_cmd('-x', irb_cmd), **options)
    end

    def check_wx_config
      false
    end

    def wx_config(_option)
      nil
    end

    def check_tool_pkgs
      []
    end

    def download_file(_url, _dest)
      raise NoMethodError
    end

    def install_prerequisites
      pkg_deps = check_tool_pkgs
      if get_config('autoinstall') == false
        $stderr.puts <<~__ERROR_TXT
          ERROR: This system lacks installed versions of the following required software packages:
            #{pkg_deps.join(', ')}
            
            Install these packages and try again.
        __ERROR_TXT
        exit(1)
      end
      pkg_deps
    end

    # only called after src gem build
    def cleanup_prerequisites
      # noop
    end

    def wants_autoinstall?
      flag = get_config('autoinstall')
      if flag.nil?
        $stdout.puts <<~__Q_TEXT

          [ --- ATTENTION! --- ]
          wxRuby3 requires some software packages to be installed before being able to continue building.
          If you like these can be automatically installed next (if you are building the source gem the
          software will be removed again after building finishes).
          Do you want to have the required software installed now? [yN] : 
        __Q_TEXT
        answer = $stdin.gets(chomp: true).strip
        while !answer.empty? && !%w[Y y N n].include?(answer)
          $stdout.puts 'Please answer Y/y or N/n [Yn] : '
          answer = $stdin.gets(chomp: true).strip
        end
        flag = %w[Y y].include?(answer)
      end
      flag
    end

    def get_config(key)
      Config.get_config(key)
    end

    def get_cfg_string(key)
      Config.get_cfg_string(key)
    end

    def set_config(key, val)
      Config.set_config(key, val)
    end

    def dll_mask
      "#{dll_ext}*"
    end

    def do_link(_pkg)
    end

    def get_rpath_origin
      ''
    end
    protected :get_rpath_origin

    def patch_rpath(_shlib, *)
      true
    end
    protected :patch_rpath

    def update_shlib_loadpaths(_shlib)
      true
    end

    def update_shlib_ruby_libpath(_shlib)
      true
    end

    def update_shlib_wxwin_libpaths(_shlib, _deplibs)
      true
    end

    class AnyOf
      def initialize(*features)
        @features = features
      end
      attr_reader :features

      def hash
        @features.hash
      end

      def eql?(other)
        self.class === other && @features.eql?(other.features)
      end
    end

    class << self

      def rb_version
        @rb_version ||= RUBY_VERSION.split('.').collect {|n| n.to_i}
      end

      def rb_ver_major
        rb_version[0]
      end

      def rb_ver_minor
        rb_version[1]
      end

      def rb_ver_release
        rb_version[2]
      end

      def build_cfg
        File.join(WXRuby3::ROOT, WXRuby3::BUILD_CFG)
      end

      def save
        cfg = WXRuby3::CONFIG.dup
        wxw_system = !!cfg.delete(WXW_SYS_KEY)
        cfg['wxwin'] = '@system' if wxw_system
        File.open(build_cfg, 'w') do |f|
          f << JSON.pretty_generate(cfg)
        end
      end

      def load
        if File.file?(build_cfg)
          File.open(build_cfg, 'r') do |f|
            cfg = JSON.load(f.read)
            if cfg['wxwin'] == '@system'
              cfg[WXW_SYS_KEY] = true
              cfg.delete('wxwin')
            end
            WXRuby3::CONFIG.merge!(cfg)
          end
        end
      end

      def wxruby_root
        WXRuby3::ROOT
      end

      def platform
        case RUBY_PLATFORM
        when /mingw/
          :mingw
        when /freebsd/
          :freebsd
        when /darwin/
          :macosx
        when /linux/
          :linux
        else
          :unknown
        end
      end

      def create
        load # load the build config (if any)
        klass = Class.new do
          include FileUtils

          include Config

          def initialize
            @ruby_exe = RB_CONFIG["ruby_install_name"]

            @sysinfo = Plat4m.current rescue nil
            @platform = if @sysinfo
                          case @sysinfo.os.id
                          when :darwin
                            :macosx
                          when :windows
                            RUBY_PLATFORM =~ /mingw/ ? :mingw : :unknown
                          else
                            @sysinfo.os.id
                          end
                        else
                          :unknown
                        end
            require_relative File.join('config', @platform.to_s)
            self.class.include(WXRuby3::Config::Platform)

            init # initialize settings
          end

          attr_reader :ruby_exe, :sysinfo, :platform, :helper_modules, :helper_inits, :include_modules, :verbosity
          attr_reader :release_build, :debug_build, :verbose_debug, :no_deprecate
          attr_reader :ruby_cppflags, :ruby_ldflags, :ruby_libs, :extra_cflags, :extra_cppflags, :extra_ldflags,
                      :extra_libs, :cpp_out_flag, :link_output_flag, :obj_ext, :dll_ext,
                      :cxxflags, :libs, :cpp, :ld, :verbose_flag
          attr_reader :wx_port, :wx_path, :wx_cppflags, :wx_libs, :wx_setup_h, :wx_xml_path
          attr_reader :swig_major, :swig_dir, :swig_path, :src_dir, :src_path, :src_gen_dir, :src_gen_path, :obj_dir, :obj_path,
                      :rake_deps_dir, :rake_deps_path, :dest_dir, :test_dir, :classes_dir, :classes_path,
                      :common_dir, :common_path, :interface_dir, :interface_path,
                      :ext_dir, :ext_path, :wxruby_dir, :wxruby_path, :exec_env
          attr_reader :rb_lib_dir, :rb_lib_path, :rb_events_dir, :rb_events_path,
                      :rb_doc_dir, :rb_doc_path, :rb_docgen_dir, :rb_docgen_path

          # (re-)initialize settings
          def init
            # STANDARD DIRECTORIES
            @ext_dir = 'ext'
            @ext_path = File.join(Config.wxruby_root, @ext_dir)
            @wxruby_dir = File.join(@ext_dir, 'wxruby3')
            @wxruby_path = File.join(@ext_path, 'wxruby3')
            @swig_dir = File.join(@wxruby_dir,'swig')
            @swig_path = File.join(Config.wxruby_root, @swig_dir)
            @rake_deps_dir = File.join('rakelib', 'deps')
            @rake_deps_path = File.join(Config.wxruby_root, @rake_deps_dir)
            @src_dir = File.join(@wxruby_dir,'src')
            @src_path = File.join(Config.wxruby_root, @src_dir)
            @src_gen_dir = File.join(@src_dir, '.generate')
            @src_gen_path = File.join(Config.wxruby_root, @src_gen_dir)
            @obj_dir = File.join(@wxruby_dir,'obj')
            @obj_path = File.join(Config.wxruby_root, @obj_dir)
            @dest_dir = File.join(Config.wxruby_root, 'lib')
            @test_dir = File.join(Config.wxruby_root, 'tests')
            @classes_dir = File.join(@swig_dir, 'classes')
            @classes_path = File.join(Config.wxruby_root, @classes_dir)
            @common_dir = File.join(@classes_dir, 'common')
            @common_path = File.join(Config.wxruby_root, @common_dir)
            @interface_dir = File.join(@classes_dir, 'include')
            @interface_path = File.join(Config.wxruby_root, @interface_dir)
            @rb_lib_dir = 'lib'
            @rb_lib_path = File.join(Config.wxruby_root, @rb_lib_dir)
            @rb_doc_dir = File.join(@rb_lib_dir, 'wx', 'doc')
            @rb_doc_path = File.join(Config.wxruby_root, @rb_doc_dir)
            @rb_docgen_dir = File.join(@rb_doc_dir, 'gen')
            @rb_docgen_path = File.join(Config.wxruby_root, @rb_docgen_dir)

            # Extra swig helper files to be built
            @helper_modules = %w|RubyStockObjects|
            # helper to initialize on startup (stock objects can only be initialized after App creation)
            @helper_inits = @helper_modules - %w|RubyStockObjects|

            # included swig specfiles not needing standalone processing
            @include_modules =
              %w|common.i typedefs.i typemap.i mark_free_impl.i memory_management.i shared/*.i|.collect do |glob|
                Dir.glob(File.join(@swig_dir, glob))
              end.flatten


            @debug_build   = WXRuby3::CONFIG['with-debug']
            @release_build = !@debug_build
            @verbosity     = ENV['WXRUBY_VERBOSE'] ? (ENV['WXRUBY_VERBOSE'] || '1').to_i : 0

            @dynamic_build = !!ENV['WXRUBY_DYNAMIC']
            @static_build  = !!ENV['WXRUBY_STATIC']

            @no_deprecate = !(!!ENV['WX_KEEP_DEPRECATE'])


            @ruby_includes = [ RB_CONFIG["rubyhdrdir"],
                               RB_CONFIG["sitehdrdir"],
                               RB_CONFIG["vendorhdrdir"],
                               RB_CONFIG['rubyarchhdrdir'] ?
                                 RB_CONFIG['rubyarchhdrdir'] :
                                 File.join(RB_CONFIG["rubyhdrdir"], RB_CONFIG['arch'])
                              ].compact
            @ruby_includes << File.join(@wxruby_path, 'include')

            @ruby_cppflags    = [RB_CONFIG["CFLAGS"]].compact
            @ruby_ldflags     = [RB_CONFIG['LDFLAGS'], RB_CONFIG['DLDFLAGS'], RB_CONFIG['ARCHFLAG']].compact
            @ruby_libs        = []
            @extra_cppflags   = ['-DSWIG_TYPE_TABLE=wxruby3']
            @extra_cflags     = []
            @extra_ldflags    = []
            @extra_libs       = []
            @cpp_out_flag     =  '-o '
            @link_output_flag = '-o '

            @obj_ext          = RB_CONFIG["OBJEXT"]
            @dll_ext          = RB_CONFIG['DLEXT']

            # Exclude certain classes from being built, even if they are present
            # in the configuration of wxWidgets.
            if ENV['WXRUBY_EXCLUDED']
              ENV['WXRUBY_EXCLUDED'].split(",").each { |classname| exclude_module(classname) }
            end

            @exec_env         = {}

            # platform specific initialization
            init_platform

            if @wx_xml_path.empty?
              @wx_xml_path = File.join(@ext_path, 'wxWidgets', 'docs', 'doxygen', 'out', 'xml')
            end

            @verbose_flag = ''
            if @debug_build
              @verbose_flag << '-D__WXRB_DEBUG__=1'
            end

            # SIXTH: Putting it all together

            # Flags to be passed to the C++ compiler
            @cxxflags = [@wx_cppflags, @ruby_cppflags, @extra_cflags, @extra_cppflags ].flatten.join(' ')

            # Flags to be passed to the linker
            @ldflags  = [ @ruby_ldflags, @extra_ldflags ].flatten.join(' ')

            # Libraries that the linker should build
            @libs     = [ @wx_libs, @ruby_libs, @extra_libs ].flatten.join(' ')
          end

          def report
            if @debug_build
              log_progress("Enabled DEBUG build")
            else
              log_progress("Enabled RELEASE build")
            end
          end

          def verbose?
            @verbosity>0
          end

          def is_configured?
            File.file?(File.join(WXRuby3::ROOT, WXRuby3::BUILD_CFG))
          end

          def is_bootstrapped?
            is_configured? && File.directory?(wx_xml_path)
          end

          def with_wxwin?
            get_config('with-wxwin')
          end

          def with_wxhead?
            get_config('with-wxhead')
          end

          def wx_version
            @wx_version || ''
          end

          def wx_version_check(ver)
            @wx_version_list ||= (@wx_version || '0.0.0').split('.').collect {|s| s.to_i }
            ver = ver.split('.').collect {|s| s.to_i }  unless ::Array === ver
            @wx_version_list <=> ver
          end

          def mingw?
            @platform == :mingw
          end

          def freebsd?
            @platform == :freebsd
          end

          def macosx?
            @platform == :macosx
          end

          def linux?
            @platform == :linux
          end

          def windows?
            mingw?
          end

          def ldflags(_target)
            @ldflags
          end

          def has_wxwidgets_xml?
            File.directory?(@wx_xml_path)
          end

          def build_paths
            [ rake_deps_path, src_path, src_gen_path, obj_path, classes_path, common_path, interface_path ]
          end

          def wx_gitref
            if @wx_version
              "v#{@wx_version}"
            elsif get_config('with-wxhead')
              'master'
            else
              nil
            end
          end
          private :wx_gitref

          def do_bootstrap
            install_prerequisites
            # do we have a local wxWidgets tree already?
            unless File.directory?(File.join(ext_path, 'wxWidgets', 'docs', 'doxygen'))
              wx_checkout
            end
            # do we need to build wxWidgets?
            if get_config('with-wxwin') && get_cfg_string('wxwin').empty?
              chdir(File.join(ext_path, 'wxWidgets')) do
                wx_build
              end
            end
            # generate the doxygen XML output
            wx_generate_xml
            # now we need to respawn the rake command in place of this process
            respawn_rake
          end

          def cleanup_bootstrap
            rm_rf(File.join(ext_path, 'wxWidgets'), verbose: !WXRuby3.config.run_silent?) if File.directory?(File.join(ext_path, 'wxWidgets'))
            cleanup_prerequisites
          end

          # Testing the relevant wxWidgets setup.h file to see what
          # features are supported.

          # The wxWidgets setup.h file contains a series of definitions like
          # #define wxUSE_FOO 1. The location of the file should be set
          # by the platform-specific rakefile. Parse it into a ruby hash:
          def features
            @features ||= _retrieve_features(wx_setup_h)
          end

          def any_feature_set?(*featureset)
            featureset.any? do |feature|
              if ::Array === feature
                features_set?(*feature)
              else
                !!features[feature.to_s]
              end
            end
          end
          private :any_feature_set?

          def features_set?(*featureset)
            featureset.flatten.all? do |feature|
              if AnyOf === feature
                any_feature_set?(*feature.features)
              else
                !!features[feature.to_s]
              end
            end
          end

          def excluded_module?(module_spec)
            explicit_excluded_modules.include?(module_spec.module_name) || !features_set?(*module_spec.requirements)
          end

          def exclude_module(module_name)
            explicit_excluded_modules << module_name
          end

          private

          def explicit_excluded_modules
            @explicit_excluded_modules ||= []
          end

          def _retrieve_features(wxwidgets_setup_h)
            features = {}

            if is_configured? && wxwidgets_setup_h
              File.read(wxwidgets_setup_h).scan(/^\s*#define\s+(wx\w+|__\w+__)\s+([01]|wx\w+)/) do | define |
                val_str = $2
                feat_str = $1
                if val_str.start_with?('wx')
                  feat_val = !!features[val_str.sub(/\Awx/i, '')]
                else
                  feat_val = val_str.to_i.zero? ? false : true
                end
                feat_id = feat_str.sub(/\Awx/i, '').gsub(/\A__|__\Z/, '')
                features[feat_id] = feat_val
              end
              # make sure correct platform defines are included as well which will not be the case always
              # (for example __WXMSW__ and __WXOSX__ are not in setup.h)
              features['WXMSW'] = true if features['GNUWIN32']
              features['WXOSX'] = true if features['DARWIN']
              # prior to wxWidgets 3.3 this feature was not set for wxGTK builds
              features['WXGTK'] = true if features['LINUX'] && !features['WXGTK'] && wx_port == :wxgtk
            end

            features
          end

        end
        klass.new
      end
      private :create

      def instance
        @instance ||= create
      end

      def get_config(key)
        v = if WXRuby3::CONFIG.has_key?(key.to_s)
              WXRuby3::CONFIG[key.to_s]
            else
              RB_DEFAULTS.include?(key.to_s) ? RB_CONFIG[key.to_s] : nil
            end
        v = WXRuby3::CONFIG[v[1,v.size]] while String === v && v.start_with?('$') && WXRuby3::CONFIG.has_key?(v[1,v.size])
        v
      end

      def get_cfg_string(key)
        get_config(key) || ''
      end

      def set_config(key, val)
        WXRuby3::CONFIG[key.to_s] = val
      end

      def is_configured?
        instance.is_configured?
      end

      def is_bootstrapped?
        instance.is_bootstrapped?
      end

    end # class << self

  end # module Config

  def self.build_cfg
    Config.build_cfg
  end

  def self.config
    Config.instance
  end

  def self.is_configured?
    Config.is_configured?
  end

  def self.is_bootstrapped?
    Config.is_bootstrapped?
  end

end # module WXRuby3

Dir.glob(File.join(File.dirname(__FILE__), 'ext', '*.rb')).each do |fn|
  require fn
end
