require 'gir_ffi/builder_helper'
require 'gir_ffi/module_base'
require 'gir_ffi/builder/function'

module GirFFI
  # Builds a module based on information found in the introspection
  # repository.
  class Builder::Module
    include BuilderHelper

    def initialize namespace, version=nil
      @namespace = namespace
      @version = version
      @safe_namespace = @namespace.gsub(/^(.)/) { $1.upcase }
    end

    def generate
      build_module
    end

    def setup_function method
      go = function_introspection_data method.to_s

      return false if go.nil?

      modul = build_module
      lib = modul.const_get(:Lib)

      Builder.attach_ffi_function lib, go

      meta = (class << modul; self; end)
      meta.class_eval function_definition(go, lib)

      true
    end

    def build_class classname
      info = gir.find_by_name @namespace, classname.to_s
      Builder.build_class info
    end

    def build_module
      unless defined? @module
        build_dependencies
        build_module_non_recursive
      end
      @module
    end

    def build_module_non_recursive
      unless defined? @module
        instantiate_module
        setup_lib_for_ffi
        setup_module unless already_set_up
      end
      @module
    end

    private

    def build_dependencies
      deps = gir.dependencies @namespace
      deps.each {|dep|
        name, version = dep.split '-'
        Builder.build_module_non_recursive name, version
      }
    end

    def instantiate_module
      @module = get_or_define_module ::Object, @safe_namespace
    end

    def setup_module
      @module.extend ModuleBase
      @module.const_set :GIR_FFI_BUILDER, self
      begin
        require "gir_ffi/overrides/#{@namespace.downcase}"
        @module.class_eval "include GirFFI::Overrides::#{@namespace}"
      rescue LoadError
      end
    end

    def already_set_up
      @module.respond_to? :method_missing
    end

    def setup_lib_for_ffi
      @lib = get_or_define_module @module, :Lib

      unless (class << @lib; self.include? FFI::Library; end)
        @lib.extend FFI::Library
        @lib.ffi_lib_flags :global, :lazy
        libspec = gir.shared_library(@namespace)
        unless libspec.nil?
          @lib.ffi_lib(*libspec.split(/,/))
        end
      end

      optionally_define_constant(@lib, :CALLBACKS) { [] }
    end

    def function_introspection_data function
      info = gir.find_by_name @namespace, function.to_s
      return nil if info.nil?
      info.info_type == :function ? info : nil
    end

    def function_definition info, libmodule
      Builder::Function.new(info, libmodule).generate
    end

    def gir
      unless defined? @gir
        @gir = IRepository.default
        @gir.require @namespace, @version
      end
      @gir
    end

    def get_or_define_module parent, name
      optionally_define_constant(parent, name) { Module.new }
    end

  end
end
