module GirFFI
  module Overrides
    module GLib
      def self.included base
	base.extend ClassMethods
        extend_classes(base)
        attach_non_introspectable_functions(base)
      end

      def self.extend_classes base
        base::SList.class_eval {
          attr_accessor :element_type
          include ListInstanceMethods
          extend ListClassMethods
          include Enumerable
        }
        base::List.class_eval {
          attr_accessor :element_type
          include ListInstanceMethods
          extend ListClassMethods
          include Enumerable
        }
        base::HashTable.class_eval {
          attr_accessor :key_type
          attr_accessor :value_type
          include HashTableInstanceMethods
          extend HashTableClassMethods
          include Enumerable
        }
        base::ByteArray.class_eval {
          include ByteArrayInstanceMethods
        }
        base::Array.class_eval {
          attr_accessor :element_type
          include ArrayInstanceMethods
        }
      end

      def self.attach_non_introspectable_functions base
        base::Lib.attach_function :g_slist_prepend, [:pointer, :pointer],
          :pointer
        base::Lib.attach_function :g_list_append, [:pointer, :pointer],
          :pointer
        base::Lib.attach_function :g_hash_table_foreach,
          [:pointer, base::HFunc, :pointer], :void
        base::Lib.attach_function :g_hash_table_new,
          [base::HashFunc, base::EqualFunc], :pointer
        base::Lib.attach_function :g_hash_table_insert,
          [:pointer, :pointer, :pointer], :void
        base::Lib.attach_function :g_byte_array_new, [], :pointer
        base::Lib.attach_function :g_byte_array_append,
          [:pointer, :pointer, :uint], :pointer
        base::Lib.attach_function :g_array_new, [:int, :int, :uint], :pointer
        base::Lib.attach_function :g_array_append_vals,
          [:pointer, :pointer, :uint], :pointer
      end

      module ClassMethods
        # FIXME: Turn into real constructor
        def slist_new elmttype
          ::GLib::List._real_new(FFI::Pointer.new(0)).tap {|it|
            it.element_type = elmttype}
        end

        # FIXME: Turn into instance method; Use element type.
        def slist_prepend slist, data
          ::GLib::SList.wrap(slist.element_type, ::GLib::Lib.g_slist_prepend(slist, data))
        end

        # FIXME: Turn into real constructor
        def list_new elmttype
          ::GLib::List._real_new(FFI::Pointer.new(0)).tap {|it|
            it.element_type = elmttype}
        end

        # FIXME: Turn into instance method; Use element type.
        def list_append list, data
          ::GLib::List.wrap(list.element_type, ::GLib::Lib.g_list_append(list, data))
        end

        # FIXME: Turn into real constructor
        def hash_table_new keytype, valtype
          hash_fn, eq_fn = case keytype
                           when :utf8
                             lib = ::GLib::Lib.ffi_libraries.first
                             [ FFI::Function.new(:uint, [:pointer], lib.find_function("g_str_hash")),
                               FFI::Function.new(:int, [:pointer, :pointer], lib.find_function("g_str_equal"))]
                           else
                             [nil, nil]
                           end

          ::GLib::HashTable.wrap(keytype, valtype, ::GLib::Lib.g_hash_table_new(hash_fn, eq_fn))
        end

        # FIXME: Turn into real constructor
        def byte_array_new
          ::GLib::ByteArray.wrap(::GLib::Lib.g_byte_array_new)
        end

        # FIXME: Turn into instance method
        def byte_array_append arr, data
          bytes = GirFFI::ArgHelper.utf8_to_inptr data
          len = data.bytesize
          ::GLib::ByteArray.wrap(::GLib::Lib.g_byte_array_append arr.to_ptr, bytes, len)
        end

        # FIXME: Turn into real constructor
        def array_new type
          ffi_type = type == :utf8 ? :pointer : type
          arr = ::GLib::Array.wrap(
            ::GLib::Lib.g_array_new(0, 0, FFI.type_size(ffi_type)))
          arr.element_type = type
          arr
        end

        # FIXME: Turn into instance method
        def array_append_vals arr, data
          bytes = GirFFI::ArgHelper.typed_array_to_inptr arr.element_type, data
          len = data.length
          res = ::GLib::Array.wrap(
            ::GLib::Lib.g_array_append_vals(arr.to_ptr, bytes, len))
          res.element_type = arr.element_type
          res
        end

      end

      module ListInstanceMethods
        def each
          list = self
          rval = nil
          until list.nil?
            rval = yield GirFFI::ArgHelper.cast_from_pointer(element_type, list[:data])
            list = self.class.wrap(element_type, list[:next])
          end
          rval
        end
      end

      module ListClassMethods
        def wrap elmttype, ptr
          super(ptr).tap do |it|
            break if it.nil?
            it.element_type = elmttype
          end
        end
      end

      module HashTableClassMethods
        def wrap keytype, valtype, ptr
          super(ptr).tap do |it|
            break if it.nil?
            it.key_type = keytype
            it.value_type = valtype
          end
        end
      end

      module HashTableInstanceMethods
        def each
          prc = Proc.new {|keyptr, valptr, userdata|
            key = cast_from_pointer key_type, keyptr
            val = cast_from_pointer value_type, valptr
            yield key, val
          }
          ::GLib::Lib.g_hash_table_foreach self.to_ptr, prc, nil
        end

        def to_hash
          Hash[self.to_a]
        end

        def insert key, value
          keyptr = cast_to_pointer key_type, key
          valptr = cast_to_pointer value_type, value
          ::GLib::Lib.g_hash_table_insert self.to_ptr, keyptr, valptr
        end

        def cast_to_pointer type, it
          if type == :utf8
            GirFFI::ArgHelper.utf8_to_inptr it
          else
            FFI::Pointer.new(it)
          end
        end

        def cast_from_pointer type, it
          case type
          when :utf8
            GirFFI::ArgHelper.ptr_to_utf8 it
          when :gint32
            GirFFI::ArgHelper.cast_pointer_to_int32 it
          else
            it.address
          end
        end
      end

      module ByteArrayInstanceMethods
        def to_string
          GirFFI::ArgHelper.ptr_to_utf8_length self[:data], self[:len]
        end
      end

      module ArrayInstanceMethods
        def to_a
          GirFFI::ArgHelper.ptr_to_typed_array(self.element_type,
                                               self[:data], self[:len])
        end
      end
    end
  end
end

