
# 
# Copyright (C) 2006-2023 Matthias Koefferlein
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
#

class CPPObject

  # Propagate setting of the visibility down to the children 
  # (set_visibility will replace the default visibility with public or private)
  def set_visibility
  end

  # Inject scoped objects (like "class A::B { ... };") into their target 
  # scope.
  def inject_scoped
  end

  # delivers a string representation of the name or nil if the object does not have a name 
  # to contribute
  def myself
    nil
  end

  # delivers a string representation of the "weak" name or nil if the object does not have a name 
  # to contribute. "weak names" are such of second order - e.g. forward declarations.
  def myself_weak
    nil
  end

  # delivers a CPPQualifiedId representation of the object's location or nil if the object
  # does not have a location to contribute
  def myid
    nil
  end

  # sets the CPPQualifiedId
  def setid(id)
  end

  # supposed to establish the parent link
  def set_parent(p)
  end

  # removes a child from our members
  def remove(d)
  end

  # inserts a child into our members
  def insert(d)
  end

end

class CPPType
  
  def func
    i = self
    while i.respond_to?(:inner)
      if i.is_a?(CPPFunc) && (i.inner.is_a?(CPPQualifiedId) || i.inner.is_a?(CPPAnonymousId))
        return i
      end
      i = i.inner
    end
    nil
  end

  def return_type
    rt = self.dup
    f = self.func
    if f
      i = self
      idup = rt
      while i.respond_to?(:inner)
        i = i.inner
        if i == f
          idup.inner = CPPAnonymousId::new
          break
        end
        idup.inner = i.dup
        idup = idup.inner
      end
    end
    rt
  end

  def name_substituted_type(sub)
    rt = self.dup
    i = self
    idup = rt
    while i.respond_to?(:inner)
      ii = i.inner
      if ii.is_a?(CPPQualifiedId) || ii.is_a?(CPPAnonymousId)
        idup.inner = sub
        break
      end
      i = ii
      idup.inner = i.dup
      idup = idup.inner
    end
    rt
  end

  def anonymous_type
    name_substituted_type(CPPAnonymousId::new)
  end

  def renamed_type(name)
    name_substituted_type(CPPQualifiedId::new(false, [name]))
  end

  def name
    i = self
    while i.respond_to?(:inner)
      ii = i.inner
      if ii.is_a?(CPPQualifiedId)
        return ii.to_s
      end
      i = ii
    end
    nil
  end

  def is_void?
    self.concrete.is_a?(CPPPOD) && self.concrete.to_s == "void" && (self.inner.is_a?(CPPAnonymousId) || self.inner.is_a?(CPPQualifiedId))
  end

end

module QualifiedNameResolver

  attr_accessor :parent

  def global_scope
    o = self
    while o.myself && o.parent
      o = o.parent
    end
    o
  end

  # requirements
  #  - children -> must deliver a list of child objects
  #  - myself -> must deliver my name

  def set_parent(parent = nil)
    self.parent = parent
    @id2obj = {}
    self.children.each do |d|
      d.myself && (@id2obj[d.myself] = d)
    end 
    self.children.each do |d|
      d.myself_weak && (@id2obj[d.myself_weak] ||= d)
    end 
    self.children.each do |d|
      d.set_parent(self)
    end
    # Add other children, for example contributed by base classes
    if self.respond_to?(:other_children)
      self.other_children.each do |d|
        d.myself && (@id2obj[d.myself] = d)
      end
    end
  end

  def dump_ids
    @id2obj.keys.sort.each do |k|
      puts("--> #{k}")
    end
  end

  # by default the objects don't have a weak identity
  def myself_weak
    nil
  end

  # returns a list of names of child objects
  def ids
    (@id2obj || {}).keys.sort
  end

  def id2obj(id)
    @id2obj && @id2obj[id]
  end

  def resolve_qid(qid, stop = nil, include_other = true)

    qid.is_a?(CPPQualifiedId) || raise("Argument of resolve_qid must be a CPPQualifiedId object")

    obj = nil
    if qid.global && self.parent
      root = self
      while root.parent
        root = root.parent
      end
      obj = root.resolve_qid(qid, nil, false)
    else
      obj = id2obj(qid.parts[0].id)
      if obj && qid.parts.size > 1
        # The part may be a typedef: resolve it in that case before we proceed
        while obj && obj.is_a?(CPPTypedef) 
          obj = obj.type.concrete.is_a?(CPPQualifiedId) && self.resolve_qid(obj.type.concrete, stop, include_other)
        end
        if obj
          qid_new = qid.dup
          qid_new.parts = qid.parts[1 .. -1]
          obj = obj.respond_to?(:resolve_qid) && obj.resolve_qid(qid_new, stop, include_other)
        end
      end
      if ! obj && include_other
        # try base classes
        self.other_children.each do |bc|
          if bc != self && bc.respond_to?(:resolve_qid)
            (obj = bc.resolve_qid(qid, self, false)) && break
          end
        end
      end
      if ! obj && self.parent && self.parent != stop
        obj = self.parent.resolve_qid(qid, stop, include_other)
      end
    end

    obj

  end

  def inject_scoped

    self.children.each do |d|

      d.inject_scoped

      qid = d.myid
      if qid

        qid.is_a?(CPPQualifiedId) || raise("Argument of resolve_qid must be a CPPQualifiedId object")

        if qid.parts.size > 1

          qid = qid.dup
          while qid.parts.size > 1
            obj = id2obj(qid.parts[0].id)
            if obj
              qid.parts = qid.parts[1 .. -1]
            else
              break
            end
          end

          if obj && qid.parts.size == 1
            # This copies the visibility which is not quite correct, since the injection case
            # is usually used for providing an implementation outside a class. That does not 
            # mean the outside implementation will provide the visibility. Instead a forward
            # declaration inside the target scope will do. Since that is lost in our implementation
            # currently that is not possible.
            self.remove(d)
            d.setid(qid)
            obj.insert(d)
          end

        end

      end

    end

  end

end

class CPPDeclaration

  include QualifiedNameResolver

  def children
    []
  end

  def other_children
    []
  end

  def myself
    self.type.name 
  end

end

class CPPEnumDeclaration

  include QualifiedNameResolver

  def children
    []
  end

  def other_children
    []
  end

  def myself
    # exclude forward declarations
    self.enum.specs && self.enum.name.to_s
  end

end

class CPPEnumSpec

  include QualifiedNameResolver

  def children
    []
  end

  def other_children
    []
  end

  def myself
    self.name.to_s
  end

end

class CPPStruct

  attr_accessor :parent

  def global_scope
    self.parent && self.parent.global_scope
  end

  def set_visibility

    (self.body_decl || []).each do |bd|
      if bd.respond_to?(:visibility) && bd.visibility == :default
        if self.kind == :struct || self.kind == :union
          bd.visibility = :public
        else 
          bd.visibility = :private
        end
      end
      bd.set_visibility
    end

  end

end

class CPPTypedef

  include QualifiedNameResolver

  def myself
    self.type.name
  end

  def children
    []
  end

  def other_children
    []
  end

end

class CPPStructDeclaration

  include QualifiedNameResolver

  def children

    # take this chance to set the parent to struct
    self.struct.parent = self
    c = self.struct.body_decl || []

    # add enum constants (CPPEnumSpec)
    (self.struct.body_decl || []).each do |bd|
      if bd.is_a?(CPPEnumDeclaration) && bd.enum && bd.enum.specs && !bd.enum.is_class
        c += bd.enum.specs
      end
    end

    c

  end

  def remove(d)
    self.struct.body_decl && self.struct.body_decl.delete(d)
  end

  def insert(d)
    self.struct.body_decl ||= []
    self.struct.body_decl << d
  end

  def other_children

    # add base classes both as sub-namespace and individual parts
    # and add self so scoping is possible into ourself 
    c = [ self ]

    (self.struct.base_classes || []).each do |bc|
      # The parent may be null for template base classes which are 
      # forward-declared .. we're not interested in this case.
      if self.parent
        bc_obj = self.parent.resolve_qid(bc.class_id, nil, false)
        # NOTE: it may look strange to test whether the base class is the class itself but
        # since we do a half-hearted job of resolving template variants, this may happen
        # if we derive a template specialization from another one (specifically 
        # "template<class T> struct is_default_constructible : is_default_constructible<> { .. }"
        if bc_obj != self && bc_obj.is_a?(CPPStructDeclaration)
          c << bc_obj
          c += bc_obj.children
          c += bc_obj.other_children
        end
      end
    end

    c

  end

  def myself_weak
    # the weak identity will also include forward declarations
    self.struct.id.to_s
  end

  def myself
    # forward declarations (struct.body_decl == nil and no base classes) don't produce a name and
    # will therefore not contribute 
    (self.struct.body_decl || self.struct.base_classes) && self.struct.id.to_s
  end

  def myid
    # forward declarations (struct.body_decl == nil and no base classes) don't produce a name and
    # will therefore not contribute 
    (self.struct.body_decl || self.struct.base_classes) && self.struct.id
  end

  def setid(id)
    self.struct.id = id
  end

  def set_visibility
    self.struct && self.struct.set_visibility
  end

end

class CPPNamespace

  include QualifiedNameResolver

  def children

    # take this opportunity to join identical namespaces
    if self.members
      new_members = []
      ns = {}
      self.members.each do |d|
        if d.is_a?(CPPNamespace)
          if !ns[d.myself]
            ns[d.myself] = d
            new_members << d
          else
            ns[d.myself].members += d.members
          end
        else
          new_members << d
        end
      end
      self.members = new_members
    end      

    self.members || []

  end

  def other_children
    # add self so scoping is possible into ourself 
    [ self ]
  end

  def myself
    self.name.to_s
  end

  def remove(d)
    self.members.delete(d)
  end

  def insert(d)
    self.members << d
  end

  def set_visibility

    (self.members || []).each do |m|
      if m.respond_to?(:visibility) && m.visibility == :default
        m.visibility = :public
      end
      m.set_visibility
    end

  end

end

class CPPModule

  include QualifiedNameResolver

  def children

    # take this opportunity to join identical namespaces
    new_decls = []
    ns = {}
    self.decls.each do |d|
      if d.is_a?(CPPNamespace)
        if !ns[d.myself]
          ns[d.myself] = d
          new_decls << d
        else
          ns[d.myself].members += d.members
        end
      else
        new_decls << d
      end
    end

    self.decls = new_decls

  end

  def other_children
    []
  end

  def remove(d)
    self.decls.delete(d)
  end

  def insert(d)
    self.decls << d
  end

  def myself
    nil
  end

  def set_visibility
    (self.decls || []).each do |d|
      if d.respond_to?(:visibility) && d.visibility == :default
        d.visibility = :public
      end
      d.set_visibility
    end
  end

end
