$LOAD_PATH << 'lib'
require 'nokogiri'
require 'active_support/inflector'
require 'cocoa'

desc "Generate module from framework"
task :generate do
  sdkroot = '/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.8.sdk'
  frameworks = ['AppKit','Foundation','CoreData','QuartzCore','CoreGraphics','CoreText','CoreFoundation']
  FileUtils.mkdir_p('tmp')
  Bundler.with_clean_env do
    `ln -s #{sdkroot}/System/Library/Frameworks/QuartzCore.framework/Versions/A/Frameworks/CoreImage.framework/Versions/A/Headers tmp/CoreImage`
    `/usr/bin/gen_bridge_metadata --no-32-bit -F complete #{frameworks.map{ |fw| "-f #{sdkroot}/System/Library/Frameworks/#{fw}.framework" }.join(' ')} -f #{sdkroot}/System/Library/Frameworks/QuartzCore.framework/Frameworks/CoreImage.framework -c "-isysroot #{sdkroot} #{frameworks.map{ |fw| "-framework #{fw}" }.join(' ')} -O0 -g -Itmp" -o tmp/Foundation.bridgesupport`
  end

  # TODO: This spaghetti can be done both dynamicly and properly
  enums = {}
  mod_methods = {}
  constants = {}
  ["tmp/Foundation.bridgesupport"].each do |filename|
    file = File.read(filename)
    xml = Nokogiri::XML(file)
    xml.xpath('//signatures/*').each do |node|
      case node.name
      when 'enum'
        enums[node['name']] = node['value64'] || node['value']
      when 'class'
        out = File.open("lib/cocoa/bindings/#{node['name']}.rb",'w')
        klass_name = ObjC::String_to_NSString(node['name'])
        klass = Cocoa::NSClassFromString(klass_name)

        out.puts "# generated by 'rake generate'"
        superclass = ObjC.msgSend_pointer(klass,'superclass')
        superclass_name = if superclass.null?
          'NSObject'
        else
          superclass_name = ObjC::NSString_to_String(Cocoa::NSStringFromClass(ObjC.msgSend_pointer(klass,'superclass')))
          out.puts "require 'cocoa/bindings/#{superclass_name}'"
          superclass_name
        end
        out.puts "module Cocoa"

        if superclass.null?
          out.puts "  class #{node['name']}"
          out.puts "    include Cocoa"
        else
          out.puts "  class #{node['name']} < Cocoa::#{superclass_name}"
        end
        methods = {}
        node.children.each do |node|
          case node.name
          when 'text'
          when 'method'
            #out.puts node.inspect
            selector = node['selector']
            arg_types = []
            retval = nil
            node.children.each do |node|
              case node.name
              when 'arg'
                arg_types[node['index'].to_i]=node['type64']||node['type']
              when 'retval'
                retval = node['type64'] || node['type']
              when 'text'
              else
                raise node.inspect
              end
            end
            method = selector.split(':').first
            next if method==nil
            method = "self.#{method}" if node['class_method']
            methods[method] ||= []
            h = { args: selector.count(':'), names: selector.split(':')[1..-1].map(&:to_sym), :types => arg_types, :retval => retval }
            h[:variadic] = true if node['variadic']
            methods[method] << h unless methods[method].include?(h)
          else
            out.puts node.name
          end
        end
        methods.each do |method,specs|
          attach = if method =~ /^self./
            method = method[5..-1]
            'attach_singular_method'
          else
            'attach_method'
          end
          if specs.size > 1
            out.puts "    #{attach} :#{method}, #{specs.inspect}"
          else
            out.puts "    #{attach} :#{method}, #{specs.first.inspect[1..-2]}"
          end
        end
        out.puts "  end"
        out.puts "end"
        out.close
      when 'function'
        #out.puts node.inspect
        selector = node['name']
        arg_types = []
        retval = nil
        node.children.each do |node|
          case node.name
          when 'arg'
            arg_types << (node['type64']||node['type'])
          when 'retval'
            retval = node['type64'] || node['type']
          when 'text'
          else
            raise node.inspect
          end
        end
        method = selector.split(':').first
        next if method==nil
        method = "self.#{method}" if node['class_method']
        mod_methods[method] ||= []
        h = { args: arg_types.size, names: selector.split(':')[1..-1], :types => arg_types, :retval => retval }
        mod_methods[method] << h unless mod_methods[method].include?(h)    when 'informal_protocol'
      when 'string_constant'
      when 'constant'
        constants[node['name']] = { :declared_type => node['declared_type64'], :type => node['type64'] }
      else
        # puts node.name
      end
    end
  end

  mod = File.open("lib/cocoa/bindings/Cocoa.rb",'w')
  mod.puts 'module Cocoa'
  mod.puts '  CONSTANTS = {'
  enums.each do |name,value|
    mod.puts "    :#{name} => #{value},"
  end
  mod.puts '  }'
  mod.puts ""

  defined = {}
  constants.each do |name,type|
    if type[:declared_type] == "NSString*" || type[:declared_type] == "CFStringRef"
      mod.puts "  attach_nsstring_getter :#{name}"
    elsif type[:type] == '{CGAffineTransform=dddddd}'
      unless defined['CGAffineTransform']
        mod.puts "  class CGAffineTransform < FFI::Struct"
        mod.puts "    layout :a,:double,:b,:double,:c,:double,:d,:double,:e,:double,:f,:double"
        mod.puts "  end"
        defined['CGAffineTransform'] = true
      end
      mod.puts "  const_set '#{name}',CGAffineTransform"
    #else
    #  mod.puts "  attach_variable :#{name}, :#{Cocoa::apple_type_to_ffi(type[:type])}"
    end
  end
  mod.puts ""

  mod_methods.each do |method,specs|
    attach = if method =~ /^self./
      method = method[5..-1]
      'attach_singular_method'
    else
      'attach_method'
    end
    if specs.size > 1
      mod.puts "  #{attach} :#{method}, #{specs.inspect}"
    else
      mod.puts "  #{attach} :#{method}, #{specs.first.inspect[1..-2]}"
    end
  end
  mod.puts "end"
  mod.close
end
