# Hard-code FFI-based Gtk+ test program. Nothing is generated here.
require 'ffi'
module GObject

  module Lib
    extend FFI::Library
    CALLBACKS = []
    ffi_lib "gobject-2.0"
    callback :GCallback, [], :void
    enum :GConnectFlags, [:AFTER, (1<<0), :SWAPPED, (1<<1)]

    attach_function :g_signal_connect_data, [:pointer, :string, :GCallback,
      :pointer, :pointer, :GConnectFlags], :ulong
  end

  def self.signal_connect_data gobject, signal, prc, data, destroy_data, connect_flags
    Lib::CALLBACKS << prc
    Lib.g_signal_connect_data gobject.to_ptr, signal, prc, data, destroy_data, connect_flags
  end
end

module Gtk
  module Lib
    extend FFI::Library

    ffi_lib "gtk-x11-2.0"
    attach_function :gtk_init, [:pointer, :pointer], :void
    attach_function :gtk_main, [], :void
    attach_function :gtk_main_quit, [], :void

    attach_function :gtk_widget_show, [:pointer], :pointer
    attach_function :gtk_widget_destroy, [:pointer], :void
    attach_function :gtk_container_add, [:pointer, :pointer], :void

    enum :GtkWindowType, [:GTK_WINDOW_TOPLEVEL, :GTK_WINDOW_POPUP]
    attach_function :gtk_window_new, [:GtkWindowType], :pointer
    attach_function :gtk_button_new, [], :pointer
    attach_function :gtk_button_new_with_label, [:string], :pointer
    attach_function :gtk_label_new, [:string], :pointer
  end

  def self.init size, ary
    argv = self.string_array_to_inoutptr ary
    argc = self.int_to_inoutptr(size)

    Lib.gtk_init argc, argv

    outsize = self.outptr_to_int argc
    outary = self.outptr_to_string_array argv, ary.nil? ? 0 : ary.size

    return outsize, outary
  end

  def self.int_to_inoutptr val
    ptr = FFI::MemoryPointer.new(:int)
    ptr.write_int val
    return ptr
  end

  # Note: This implementation would dump core if the garbage collector runs
  # before the contents of the pointers is used.
  def self.string_array_to_inoutptr ary
    ptrs = ary.map {|a| FFI::MemoryPointer.from_string(a)}
    block = FFI::MemoryPointer.new(:pointer, ptrs.length)
    block.write_array_of_pointer ptrs
    argv = FFI::MemoryPointer.new(:pointer)
    argv.write_pointer block
    argv
  end

  def self.outptr_to_int ptr
    return ptr.read_int
  end

  def self.outptr_to_string_array ptr, size
    block = ptr.read_pointer
    ptrs = block.read_array_of_pointer(size)
    return ptrs.map {|p| p.null? ? nil : p.read_string}
  end

  def self.main; Lib.gtk_main; end
  def self.main_quit; Lib.gtk_main_quit; end

  class Widget
    def show
      Lib.gtk_widget_show(@gobj)
    end
    def destroy
      Lib.gtk_widget_destroy(@gobj)
    end
    def to_ptr
      @gobj
    end
  end

  class Container < Widget
    def add widget
      Lib.gtk_container_add self.to_ptr, widget.to_ptr
    end
  end

  class Window < Container
    def initialize type
      @gobj = Lib.gtk_window_new(type)
    end
  end

  class Button < Container
    class << self
      alias :real_new :new
    end

    def self.new *args
      obj = self.real_new *args
      ptr = Lib.gtk_button_new()
      obj.instance_variable_set :@gobj, ptr
      obj
    end

    def self.new_with_label text, *args
      obj = self.real_new *args
      ptr = Lib.gtk_button_new_with_label(text)
      obj.instance_variable_set :@gobj, ptr
      obj
    end
  end

  class MyButton < Button
    def initialize sym
      puts "Initializing a #{self.class} with symbol #{sym}"
    end

    def self.new
      super :foo
    end

    def self.new_with_label sym
      super "Hello world", sym
    end
  end
end

(my_len, my_args) = Gtk.init ARGV.length + 1, [$0, *ARGV]
p [my_len, my_args]
win = Gtk::Window.new(:GTK_WINDOW_TOPLEVEL)

#btn = Gtk::Button.new_with_label('Hello World')
#btn = Gtk::MyButton.new
btn = Gtk::MyButton.new_with_label :foobar2

win.add btn

quit_prc = Proc.new { Gtk.main_quit }

# We can create callbacks with a different signature by using FFI::Function
# directly.
del_prc = FFI::Function.new(:bool, [:pointer, :pointer]) {|a, b|
  puts "delete event occured"
  true
}
GObject.signal_connect_data(win, "destroy", quit_prc, nil, nil, 0)
GObject.signal_connect_data(win, "delete-event", del_prc, nil, nil, 0)
GObject.signal_connect_data(btn, "clicked", Proc.new { win.destroy }, nil, nil, :SWAPPED)

btn.show
win.show
Gtk.main
