# This module provides an interface to the top level bits of GObject
# via ruby-ffi.
#
# Author::    John Cupitt  (mailto:jcupitt@gmail.com)
# License::   MIT

require 'ffi'
require 'forwardable'

module GObject

  # we have a number of things we need to inherit in different ways:
  #
  # - we want to be able to subclass GObject in Ruby in a simple way
  # - the layouts of the nested structs need to inherit
  # - we need to be able to cast between structs which share a base struct
  #   without creating new wrappers or messing up refcounting
  # - we need automatic gobject refcounting
  #
  # the solution is to split the class into four areas which we treat
  # differently:
  #
  # - we have a "wrapper" Ruby class to allow easy subclassing ... this has a
  #   @struct member which holds the actual pointer
  # - we use "forwardable" to forward the various ffi methods on to the
  #   @struct member ... we arrange things so that subclasses do not need to
  #   do the forwarding themselves
  # - we have two versions of the struct: a plain one which we can use for
  #   casting that will not change the refcounts
  # - and a managed one with an unref which we just use for .new
  # - we separate the struct layout into a separate module to avoid repeating
  #   ourselves

  class GObject
    extend Forwardable
    extend SingleForwardable

    def_instance_delegators :@struct, :[], :to_ptr
    def_single_delegators :ffi_struct, :ptr

    # the layout of the GObject struct
    module GObjectLayout
      def self.included base
        base.class_eval do
          layout :g_type_instance, :pointer,
                 :ref_count, :uint,
                 :qdata, :pointer
        end
      end
    end

    # the struct with unref ... manage object lifetime with this
    class ManagedStruct < FFI::ManagedStruct
      include GObjectLayout

      def self.release ptr
        # GLib::logger.debug("GObject::GObject::ManagedStruct.release") {
        #     "unreffing #{ptr}"
        # }
        ::GObject::g_object_unref ptr
      end
    end

    # the plain struct ... cast with this
    class Struct < FFI::Struct
      include GObjectLayout

    end

    # don't allow ptr == nil, we never want to allocate a GObject struct
    # ourselves, we just want to wrap GLib-allocated GObjects
    #
    # here we use ManagedStruct, not Struct, since this is the ref that will
    # need the unref
    def initialize ptr
      # GLib::logger.debug("GObject::GObject.initialize") {"ptr = #{ptr}"}
      @struct = ffi_managed_struct.new ptr
    end

    # access to the casting struct for this class
    def ffi_struct
      self.class.ffi_struct
    end

    class << self
      def ffi_struct
        self.const_get :Struct
      end
    end

    # access to the managed struct for this class
    def ffi_managed_struct
      self.class.ffi_managed_struct
    end

    class << self
      def ffi_managed_struct
        self.const_get :ManagedStruct
      end
    end

  end

  class GParamSpec < FFI::Struct
    # the first few public fields
    layout :g_type_instance, :pointer,
           :name, :string,
           :flags, :uint,
           :value_type, :GType,
           :owner_type, :GType
  end

  class GParamSpecPtr < FFI::Struct
    layout :value, GParamSpec.ptr
  end

  attach_function :g_param_spec_get_blurb, [GParamSpec.ptr], :string

  attach_function :g_object_ref, [:pointer], :void
  attach_function :g_object_unref, [:pointer], :void

end