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

###
# wxRuby3 Director specs helper mixin.
###

require 'pathname'
require 'set'

require_relative '../util/string'

module WXRuby3

  module DirectorSpecsHelper

    include Util::StringUtil

    def ifspec
      director.spec
    end

    def defmod
      director.defmod
    end

    def post_processors
      ifspec.post_processors
    end

    def interface_file
      ifspec.interface_file
    end

    def interface_include
      ifspec.interface_include
    end

    def interface_include_file
      ifspec.interface_include_file
    end

    def interface_ext_file
      File.join(ifspec.package.ruby_classes_path, 'ext', underscore(ifspec.name)+'.rb')
    end

    def module_name
      ifspec.module_name
    end

    def name
      ifspec.name
    end

    def package
      ifspec.package
    end

    def template_as_class?(tpl)
      ifspec.template_as_class?(tpl)
    end

    def template_class_name(tpl)
      ifspec.template_class_name(tpl)
    end

    def class_implementation(cls)
      ifspec.class_implementation(cls)
    end

    def classdef_name(name)
      ifspec.classdef_name(name)
    end

    def classdef_for_name(name)
      defmod.find(classdef_name(name))
    end

    def class_name(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.class_name(class_def.name)
    end

    def get_base_class(cls, hierarchy, foldedbases)
      raise "Cannot determine base class for #{cls} from multiple inheritance hierarchy : #{hierarchy}" if hierarchy.size>1
      return nil if hierarchy.empty?
      basenm, base = hierarchy.first
      return basenm unless foldedbases.include?(basenm)
      get_base_class(basenm, base.supers, folded_bases(basenm))
    end
    private :get_base_class

    def base_class(classdef_or_name, doc: false)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      if (base = ifspec.inheritance_override(class_def.name, doc: doc))
        base.name
      else
        get_base_class(class_def.name, class_def.hierarchy, folded_bases(class_def.name))
      end
    end

    def get_base_list(hierarchy, foldedbases, list = ::Set.new, do_not_fold: false)
      hierarchy.each_value do |super_def|
        list << super_def.name unless !do_not_fold && foldedbases.include?(super_def.name)
        get_base_list(super_def.supers, do_not_fold ? [] : folded_bases(super_def.name), list, do_not_fold: do_not_fold)
      end
      list
    end
    private :get_base_list

    def base_list(classdef_or_name, do_not_fold: false)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      base = ifspec.inheritance_override(class_def.name)
      hierarchy = (base && (base.name ? {base.name => base} : {})) || class_def.hierarchy
      get_base_list(hierarchy, do_not_fold ? [] : folded_bases(class_def.name), do_not_fold: do_not_fold).to_a
    end

    def get_base_module_list(hierarchy, foldedbases, list = ::Set.new)
      hierarchy.each_value do |super_def|
        list << super_def.module unless foldedbases.include?(super_def.name) || super_def.module == module_name
        get_base_module_list(super_def.supers, folded_bases(super_def.name), list)
      end
      list
    end
    private :get_base_module_list

    def base_module_list(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      base = ifspec.inheritance_override(class_def.name)
      hierarchy = (base && (base.name ? {base.name => base} : {})) || class_def.hierarchy
      get_base_module_list(hierarchy, folded_bases(class_def.name)).to_a
    end

    def is_derived_from?(classdef_or_name, base_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.is_derived_from?(class_def, base_name)
    end

    def is_folded_base?(cnm)
      ifspec.is_folded_base?(cnm)
    end

    def folded_bases(cnm)
      ifspec.folded_bases[cnm] || []
    end

    def interface_extensions(classdef_or_name, visibility='public')
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.interface_extensions(class_def.name)[visibility] || []
    end

    def is_abstract?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.abstract?(class_def.name) || (class_def.abstract && !ifspec.concrete?(class_def.name))
    end

    def explicit_concretes
      ifspec.concretes
    end

    def mixins
      ifspec.mixins
    end

    def is_mixin?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.mixin?(class_def.name)
    end

    def included_mixins
      ifspec.included_mixins
    end

    def allocate_disowned?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      ifspec.allocate_disowned?(class_def.name)
    end

    def has_virtuals?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      return class_def.all_methods.any? { |m| !(m.ignored || m.deprecated) && m.is_virtual }
    end

    def inherits_virtuals?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      base_list(class_def).any? do |base_name|
        dir = ifspec.package.director_for_class(base_name)
        raise "Cannot determine director for class #{base_name}" unless dir
        dir.synchronize do
          dir.extract_interface(false) # make sure the Director has extracted data from XML
        end
        Simple.new(dir).has_virtuals?(base_name)
      end
    end

    def gc_type(classdef)
      unless ifspec.gc_type(classdef.name)
        if classdef
          return :GC_MANAGE_AS_EVENT if classdef.is_derived_from?('wxEvent') || classdef.name == 'wxEvent'
          return :GC_MANAGE_AS_FRAME if classdef.is_derived_from?('wxFrame') || classdef.name == 'wxFrame'
          return :GC_MANAGE_AS_DIALOG if classdef.is_derived_from?('wxDialog') || classdef.name == 'wxDialog'
          return :GC_MANAGE_AS_WINDOW if classdef.is_derived_from?('wxWindow') || classdef.name == 'wxWindow'
          return :GC_MANAGE_AS_SIZER if classdef.is_derived_from?('wxSizer') || classdef.name == 'wxSizer'
          return :GC_MANAGE_AS_REFCOUNTED if classdef.is_derived_from?('wxRefCounter')
          return :GC_MANAGE_AS_OBJECT if classdef.is_derived_from?('wxObject') || classdef.name == 'wxObject'
          return :GC_MANAGE_AS_UNTRACKED
        end
      end
      ifspec.gc_type(classdef.name) || :GC_NEVER
    end

    def is_tracked?(classdef)
      gc_type(classdef) != :GC_MANAGE_AS_UNTRACKED
    end

    def includes
      ifspec.includes
    end

    def disabled_proxies
      ifspec.disabled_proxies
    end

    def no_proxies
      ifspec.no_proxies
    end

    def forced_proxy?(cls)
      ifspec.forced_proxy?(cls)
    end

    def has_proxy?(classdef_or_name)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      rc = !disabled_proxies
      rc &&= (class_def.ignored ||
          class_def.is_template? ||
          has_virtuals?(class_def) ||
          forced_proxy?(class_def.name) ||
          inherits_virtuals?(class_def))
      clsnm = class_name(class_def)
      rc &&= !no_proxies.include?(clsnm)
      rc
    end

    def has_method_proxy?(classdef_or_name, methoddef)
      return false unless methoddef.is_virtual
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      if has_proxy?(class_def)
        no_proxies.each do |decls|
          decl_clsnm, decl_mtd = decls.split('::')
          # do we have a no_proxy decl for a method in this class?
          if decl_mtd && decl_clsnm == class_name(class_def)
            decl_mtd.tr!("\n", ' ')
            # do we have a full method signature for the no_proxy decl?
            if /\A(\w+)\s*\(([^\)]*)\)\s*(const)?/ =~ decl_mtd
              # does the method name match?
              if $1 == methoddef.name
                arg_list = $2
                is_const = $3 == 'const'
                # remove any default arg values
                arg_list = arg_list.split(',').collect {|argdecl| argdecl.split('=').first.strip }
                # remove any argument names
                arg_list.collect do |argdecl|
                  lst = argdecl.split(' ')
                  argdecl = lst.shift # 'const' or type
                  argdecl << " #{lst.shift}" if argdecl == 'const' # type if 'const'
                  unless lst.empty? # name of [] left?
                    argdecl << lst.shift if lst.first == '*' || lst.first == '&'
                    if /\A([\*\&]?)\w+/ =~ lst.first
                      argdecl << $1 unless $1 == ''
                      lst.shift # loose name
                    end
                    argdecl << lst.join unless lst.empty?
                  end
                  argdecl
                end
                # in case the complete signature maches the proxy is suppressed for this method
                return false if arg_list.join.tr(' ', '') == methoddef.argument_list.tr(' ', '') && is_const == methoddef.is_const
              end
            else
              # in case the method name matches the proxy is suppressed for this method
              return false if decl_mtd == methoddef.name
            end
          end
        end
        return true # method will have proxy
      end
      false # method will not have proxy as there will not be a proxy (director) class
    end

    def declare_public?(classdef_or_name, member)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      unless member.protection == 'public'
        # in case the class can be subclassed (i.e. gets a proxy defined) we need to declare
        # non-virtual protected members (not ctor/dtor) as public in order for SWIG to generate
        # wrappers
        # we will need to postprocess the generated results to add public accessors in the proxy
        # and properly define the wrapper methods as protected
        case member
        when Extractor::MethodDef
          # only when not ctor/dtor and class is proxied and method is not proxied
          return !(member.is_ctor || member.is_dtor || !has_proxy?(class_def) || has_method_proxy?(class_def, member))
        when Extractor::MemberVarDef
          return has_proxy?(class_def)
        else
          return false
        end
      end
      true
    end

    def needs_public_override?(classdef_or_name, member)
      class_def = (Extractor::ClassDef === classdef_or_name ?
                     classdef_or_name : classdef_for_name(classdef_or_name))
      if member.protection == 'protected'
        # in case the class can be subclassed (i.e. gets a proxy defined) we need to declare
        # non-virtual protected methods (not ctor/dtor) as public in order for SWIG to generate
        # wrappers
        # we will need to postprocess the generated results to add public accessors in the proxy
        # and properly define the methods as protected
        case member
        when Extractor::MethodDef
          # only when not ctor/dtor and class is proxied and method is not proxied
          return !(member.is_ctor || member.is_dtor || !has_proxy?(class_def) || has_method_proxy?(class_def, member))
        when Extractor::MemberVarDef
          return has_proxy?(class_def)
        end
      end
      false
    end

    def disowns
      ifspec.disowns
    end

    def new_objects
      ifspec.new_objects
    end

    def warn_filters
      ifspec.warn_filters
    end

    def swig_code
      ifspec.swig_code.join("\n")
    end

    def begin_code
      ifspec.begin_code.join("\n")
    end

    def runtime_code
      ifspec.runtime_code.join("\n")
    end

    def header_code
      ifspec.header_code.join("\n")
    end

    def wrapper_code
      ifspec.wrapper_code.join("\n")
    end

    def init_code
      ifspec.init_code.join("\n")
    end

    def interface_code
      if ifspec.interface_code && !ifspec.interface_code.empty?
        if warn_filters.empty?
          ''
        else
          "\n" + warn_filters.collect do |warn, decls|
            decls.collect { |decl| "%warnfilter(#{warn}) #{decl};" }
          end.flatten.join("\n")
        end + ifspec.interface_code.join("\n")
      else
        %Q{%include "#{interface_include}"\n}
      end
    end

    def has_interface_include?
      ifspec.has_interface_include?
    end

    def extend_code(cnm)
      (ifspec.extend_code[cnm] || []).join("\n")
    end

    def swig_imports
      ifspec.swig_imports
    end

    def swig_includes
      ifspec.swig_includes
    end

    def renames
      ifspec.renames
    end

    def def_items
      defmod ? defmod.items : []
    end

    def def_item(name)
      defmod.find_item(name)
    end

    def def_classes
      defmod.classes
    end

    def no_gen?(section)
      ifspec.nogen_sections.include?(section)
    end

    def type_maps
      Typemap::Collection::Chain.new(Typemap::STANDARD, Typemap::Collection::Chain.new(director.class.type_maps, ifspec.type_maps).resolve)
    end

    class Simple
      include DirectorSpecsHelper
      def initialize(director)
        @director = director
      end

      attr_reader :director
    end

  end

end
