PLATFORMS_DIR = ENV['platforms_dir']
IOS_SDK_VERSIONS = ENV['ios_sdk_versions'].split(',')
OSX_SDK_VERSIONS = ENV['osx_sdk_versions'].split(',')

verbose(true)

task :default => :all
task :all => [:vm_files, :bridgesupport_files, :bridgesupport_static_stubs]

task :vm_files do
  strip = File.join(PLATFORMS_DIR, '../Toolchains/XcodeDefault.xctoolchain/usr/bin/strip')

  OSX_SDK_VERSIONS.each do |sdk_version|
    sdk = File.join('osx', sdk_version, 'MacOSX')
    mkdir_p sdk
    objs = "../vm/MacOSX#{sdk_version}.objs"
    Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path|
      install path, sdk
    end
    install File.join(objs, 'libmacruby-static.a'), sdk
    objs = "../vm/MacOSX#{sdk_version}-repl.objs"
    install File.join(objs, 'libmacruby-repl.dylib'), sdk

    # remove debug symbols
    Dir.glob(sdk + '/*.{a,dylib}').each { |x| sh("\"#{strip}\" -S \"#{x}\"") }
  end

  IOS_SDK_VERSIONS.each do |sdk_version|
    ios = File.join('ios', sdk_version, 'iPhoneOS')
    mkdir_p ios
    objs = "../vm/iPhoneOS#{sdk_version}.objs"
    Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path|
      install path, ios
    end
    install File.join(objs, 'libmacruby-static.a'), ios

    sim = File.join('ios', sdk_version, 'iPhoneSimulator')
    mkdir_p sim
    objs = "../vm/iPhoneSimulator#{sdk_version}.objs"
    Dir.glob(File.join(objs, 'kernel-*.bc')).each do |path|
      install path, sim
    end
    install File.join(objs, 'libmacruby-static.a'), sim
    objs = "../vm/iPhoneSimulator#{sdk_version}-repl.objs"
    install File.join(objs, 'libmacruby-repl.dylib'), sim

    # remove debug symbols
    [ios, sim].map { |x| Dir.glob(x + '/*.{a,dylib}') }.flatten.each { |x| sh("\"#{strip}\" -S \"#{x}\"") }
  end
end

def apply_bridgesupport_fixes(bs)
  rules = []
  case File.basename(bs, '.bridgesupport')
    when 'CoreMIDI', 'AudioToolBox'
      rules << [/<cftype /, '<opaque ']
    else
      rules << [/<opaque /, '<cftype ']
  end
  txt = File.read(bs)
  rules.each { |a| txt.gsub!(a[0], a[1]) }
  File.open(bs, 'w') { |io| io.write(txt) }
end

def merge_bridgesupport(base_bridgesupport, merge_brigesupport)
  return unless base_bridgesupport && merge_brigesupport
  base = File.read(base_bridgesupport)
  merge = File.read(merge_brigesupport)
  File.open(base_bridgesupport, "w") { |io|
    io.print base.lines.to_a[0..-2].join  # skip last "</signatures>" in base
    io.print merge.lines.to_a[2..-1].join # skip first "<?xml version='1.0'?>\n<signatures version='1.0'>" in merge
  }
end

EXCLUDED_FRAMEWORKS = ['Kernel', 'System', 'IOKit', 'Carbon', 'Ruby', 'RubyCocoa', 'vecLib']
task :bridgesupport_files do
  platform_dev_path = "#{PLATFORMS_DIR}/MacOSX.platform/Developer"
  OSX_SDK_VERSIONS.each do |sdk_version|
    sdk_path = "#{platform_dev_path}/SDKs/MacOSX#{sdk_version}.sdk"
    sdk_frameworks = "#{sdk_path}/System/Library/Frameworks"
    mkdir_p "osx/#{sdk_version}/BridgeSupport"
    mkdir_p "osx/#{sdk_version}/BridgeSupport/child"

    Dir.glob('*.bridgesupport').each { |bs| cp bs, "osx/#{sdk_version}/BridgeSupport" }
    Dir.glob('osx/*.bridgesupport').each { |bs| cp bs, "osx/#{sdk_version}/BridgeSupport" }

    generate_bridgesupport_file = lambda do |sdk_path, sdk_version, framework_path, is_handle_nested|
      framework = File.basename(framework_path, '.framework')
      break if EXCLUDED_FRAMEWORKS.include?(framework)
      break unless File.exist?(File.join(framework_path, framework))
      break unless File.exist?(File.join(framework_path, 'Headers'))
      dest = "osx/#{sdk_version}/BridgeSupport/#{framework}.bridgesupport"
      dest = "osx/#{sdk_version}/BridgeSupport/child/#{framework}.bridgesupport" if is_handle_nested
      unless File.exist?(dest)
        a = sdk_version.scan(/(\d+)\.(\d+)/)[0]
        sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s
        sh "/usr/bin/gen_bridge_metadata --format complete --cflags \"-isysroot #{sdk_path} -mmacosx-version-min=#{sdk_version} -D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -framework #{framework}\" --framework #{framework_path} > #{dest}"
        apply_bridgesupport_fixes(dest)
        return dest
      end
      return dest if is_handle_nested
    end

    Dir.glob(File.join(sdk_frameworks, '*.framework')).each do |framework_path|
      base_bridgesupport_path = generate_bridgesupport_file.call(sdk_path, sdk_version, framework_path, false)
      if base_bridgesupport_path
        child_framework_paths = Dir.glob(File.join(framework_path, '/Frameworks/*.framework'))
        child_framework_paths.delete_if { |x| x.include?("CoreGraphics") } if sdk_version != "10.7"
        child_framework_paths.each do |child_framework_path|
          if sdk_version == "10.7" && child_framework_path.include?("CoreGraphics")
            dest = 'osx/10.7/BridgeSupport/child/CoreGraphics.bridgesupport'
            sh "/usr/bin/gen_bridge_metadata --format complete --cflags \"-isysroot #{sdk_path} -F#{sdk_path}/System/Library/Frameworks/ApplicationServices.framework/Frameworks -mmacosx-version-min=10.7 -D__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__=100700 -framework ApplicationServices\" --framework #{child_framework_path} > #{dest}"
            apply_bridgesupport_fixes(dest)
            merge_path = dest
          else
            merge_path = generate_bridgesupport_file.call(sdk_path, sdk_version, child_framework_path, true)
          end
          merge_bridgesupport(base_bridgesupport_path, merge_path)
        end
      end
    end
  end

  platform_dev_path = "#{PLATFORMS_DIR}/iPhoneSimulator.platform/Developer"
  IOS_SDK_VERSIONS.each do |sdk_version|
    sdk_path = "#{platform_dev_path}/SDKs/iPhoneSimulator#{sdk_version}.sdk"
    sdk_frameworks = "#{sdk_path}/System/Library/Frameworks"
    mkdir_p "ios/#{sdk_version}/BridgeSupport"

    Dir.glob('*.bridgesupport').each { |bs| cp bs, "ios/#{sdk_version}/BridgeSupport" }
    Dir.glob('ios/*.bridgesupport').each { |bs| cp bs, "ios/#{sdk_version}/BridgeSupport" }

    Dir.glob(File.join(sdk_frameworks, '*.framework')).each do |framework_path|
      framework = File.basename(framework_path, '.framework')
      next if EXCLUDED_FRAMEWORKS.include?(framework)
      next unless File.exist?(File.join(framework_path, framework))
      next unless File.exist?(File.join(framework_path, 'Headers'))
      dest = "ios/#{sdk_version}/BridgeSupport/#{framework}.bridgesupport"
      unless File.exist?(dest)
        a = sdk_version.scan(/(\d+)\.(\d+)/)[0]
        sdk_version_headers = ((a[0].to_i * 10000) + (a[1].to_i * 100)).to_s
        sh "/usr/bin/gen_bridge_metadata --format complete --no-64-bit --cflags \"-isysroot #{sdk_path} -miphoneos-version-min=#{sdk_version} -D__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__=#{sdk_version_headers} -framework #{framework}\" --framework #{sdk_frameworks}/#{framework}.framework > #{dest}"
        apply_bridgesupport_fixes(dest)
      end
    end
  end
end

def generate_bs_static_stub(file, includes)
  require 'rubygems'
  require 'nokogiri'

  text = ''
  includes.each { |inc| text << "#import <#{inc}>\n" }

  doc = Nokogiri::XML(File.read(file))
  did_something = false
  doc.xpath("/signatures/function[@inline=\"true\"]").each do |node|
    name = node['name'].to_s
    retval = node.xpath('./retval')[0]['declared_type'].to_s
    args = node.xpath('./arg').map { |node| node['declared_type'].to_s }

    proto = "#{retval} __concrete__#{name}("
    proto << (0...args.size).to_a.map { |i| "#{args[i]} arg#{i}" }.join(', ')
    proto << ")"

    func = ''
    func << proto
    func << "\n{\n  "
    func << "  return " if retval != 'void'
    func << name << '('
    func << (0...args.size).to_a.map { |i| "arg#{i}" }.join(', ')
    func << ");\n}\n\n"

    text << func

    did_something = true
  end

  did_something ? text : nil
end

task :bridgesupport_static_stubs do
  OSX_SDK_VERSIONS.each do |sdk_version|
    Dir.glob("osx/#{sdk_version}/BridgeSupport/*.bridgesupport") do |bs_path|
      framework = File.basename(bs_path).sub(/\.bridgesupport/, '')

      case framework
        when 'GLKit' # TODO
          next
      end 
 
      code = "osx/#{sdk_version}/BridgeSupport/#{framework}_stubs.m"
      includes = case framework
        when 'OpenGLES'
          ['OpenGLES/EAGLDrawable.h']
        else
          ['Cocoa/Cocoa.h', "#{framework}/#{framework}.h"]
      end
      unless File.exist?(code)
        text = generate_bs_static_stub(bs_path, includes)
        next unless text
        File.open(code, 'w') { |io|
          io.puts '#pragma GCC diagnostic ignored "-Wdeprecated-declarations"'
          io.write(text)
        }
      end

      obj = "osx/#{sdk_version}/MacOSX/#{framework}_stubs.o"
      next if File.exist?(obj)
      platform_dev = "#{PLATFORMS_DIR}/MacOSX.platform/Developer"
      cflags = "-isysroot #{platform_dev}/SDKs/MacOSX#{sdk_version}.sdk"
      cflags << " -arch i386 -arch x86_64"
      cflags << " -mmacosx-version-min=#{sdk_version} -Wall -Werror -O3 -fobjc-abi-version=2 -fobjc-legacy-dispatch"
      sh "/usr/bin/clang #{cflags} #{code} -c -o #{obj}"
    end
  end

  IOS_SDK_VERSIONS.each do |sdk_version|
    Dir.glob("ios/#{sdk_version}/BridgeSupport/*.bridgesupport") do |bs_path|
      framework = File.basename(bs_path).sub(/\.bridgesupport/, '')
  
      code = "ios/#{sdk_version}/BridgeSupport/#{framework}_stubs.m"
      includes = case framework
        when 'OpenGLES'
          ['OpenGLES/EAGLDrawable.h']
        else
          ['UIKit/UIKit.h', "#{framework}/#{framework}.h"]
      end
      unless File.exist?(code)
        text = generate_bs_static_stub(bs_path, includes)
        next unless text
        File.open(code, 'w') { |io| io.write(text) }
      end

      device_archs =
        if sdk_version < '5.0'
          ['armv6', 'armv7']
        elsif sdk_version >= '6.0'
          ['armv7', 'armv7s']
        else
          ['armv7']
        end

      [['iPhoneOS', *device_archs], %w{iPhoneSimulator i386}].each do |platform, *archs|
        obj = "ios/#{sdk_version}/#{platform}/#{framework}_stubs.o"
        next if File.exist?(obj)
        platform_dev = "#{PLATFORMS_DIR}/#{platform}.platform/Developer"
        cflags = "-isysroot #{platform_dev}/SDKs/#{platform}#{sdk_version}.sdk "
        cflags << archs.map { |a| "-arch #{a}" }.join(' ')
        cflags << " -miphoneos-version-min=#{sdk_version} -Wall -Werror -O3 -fobjc-abi-version=2 -fobjc-legacy-dispatch"
        sh "#{PLATFORMS_DIR}/../Toolchains/XcodeDefault.xctoolchain/usr/bin/clang #{cflags} #{code} -c -o #{obj}"
      end
    end
  end
end

task :clean do
  IOS_SDK_VERSIONS.each { |path| rm_rf('ios/' + path) }
  OSX_SDK_VERSIONS.each { |path| rm_rf('osx/' + path) }
end
