# Copyright(C) 2012 Ruby-GNOME2 Project.
#
# This program is licenced under the same license of Ruby-GNOME2.

require "open-uri"
require "pathname"

class GNOME2WindowsBinaryBuildTask
  include Rake::DSL

  def initialize(package)
    @package = package
    define
  end

  private
  def define
    namespace :windows do
      namespace :builder do
        task :before
        define_build_tasks
        build_tasks = build_packages.collect do |package|
          "windows:builder:build:#{package.name}"
        end
        task :build => build_tasks
        task :after
      end
      desc "Build Windows binaries"
      task :build => ["windows:builder:before",
                      "windows:builder:build",
                      "windows:builder:after"]
    end
  end

  def define_build_tasks
    namespace :build do
      prepare_task_names = []
      namespace :prepare do
        prepare_task_names << "pkg_config"
        task :pkg_config do
          depended_packages = @package.windows.build_dependencies
          use_packages = [@package.name] + depended_packages
          pkg_config_path = use_packages.collect do |package|
            "../#{package}/#{@package.windows.relative_binary_dir}/lib/pkgconfig"
          end
          ENV["PKG_CONFIG_PATH"] = pkg_config_path.collect do |path|
            File.expand_path(path)
          end.join(":")
          ENV["PKG_CONFIG_LIBDIR"] = rcairo_windows_pkgconfig_path
        end

        prepare_task_names << "pkg_config_for_build"
        task :pkg_config_for_build do
          # XXX: Is it needless?
          # ENV["PKG_CONFIG_FOR_BUILD"] = "env - pkg-config"
        end
      end

      full_prepare_task_names = prepare_task_names.collect do |name|
        "windows:builder:build:prepare:#{name}"
      end
      task :prepare => full_prepare_task_names

      build_packages.each do |package|
        namespace package.name do
          task :before
          download_task = "source:downloader:download:#{package.name}"
          built_file = package.windows.built_file
          if built_file
            built_file = dist_dir + built_file
            file built_file.to_s do
              Rake::Task["windows:builder:build:prepare"].invoke
              Rake::Task[download_task].invoke
              build_package_task_body(package)
            end
            task :build => built_file.to_s
          else
            task :build => [:prepare, download_task] do
              build_package_task_body(package)
            end
          end
          task :after
        end

        prefix = "windows:builder:build:#{package.name}"
        desc "Build #{package.label} and install it into #{dist_dir}."
        task package.name => [
          "#{prefix}:before",
          "#{prefix}:build",
          "#{prefix}:after",
        ]
      end
    end
  end

  def build_package_task_body(package)
    package_tmp_dir = @package.tmp_dir + "windows" + package.name
    rm_rf(package_tmp_dir)
    mkdir_p(package_tmp_dir)

    tar_full_path = @package.download_dir + package.archive_base_name
    Dir.chdir(package_tmp_dir.to_s) do
      sh("tar", "xf", tar_full_path.to_s)
    end

    package_dir_path =
      package_tmp_dir + package.base_name + package.base_dir_in_package
    Dir.chdir(package_dir_path.to_s) do
      package.windows.patches.each do |patch|
        sh("patch -p1 < #{@package.patches_dir}/#{patch}")
      end
      if File.exist?("configure")
        configure(package)
      else
        cmake(package)
      end
      common_make_args = []
      common_make_args << "MAKE=make"
      common_make_args << "GLIB_COMPILE_SCHEMAS=glib-compile-schemas"
      if package.windows.use_cc_environment_variable?
        common_make_args << cc_env(package)
      end
      add_gobject_introspection_make_args(common_make_args)
      build_make_args = common_make_args.dup
      install_make_args = common_make_args.dup
      if package.windows.build_concurrently?
        make_n_jobs = ENV["MAKE_N_JOBS"]
        build_make_args << "-j#{make_n_jobs}" if make_n_jobs
      end
      ENV["GREP_OPTIONS"] = "--text"
      sh("nice", "make", *build_make_args) or exit(false)
      sh("make", "install", *install_make_args) or exit(false)

      package_license_dir = license_dir + package.name
      mkdir_p(package_license_dir)
      package_license_files = ["AUTHORS", "COPYING", "COPYING.LIB"]
      package_license_files = package_license_files.reject do |file|
        not File.exist?(file)
      end
      cp(package_license_files, package_license_dir)
      bundled_packages = package.bundled_packages
      bundled_packages.each do |bundled_package|
        bundled_package_license_dir = license_dir + bundled_package[:name]
        mkdir_p(bundled_package_license_dir)
        license_files = bundled_package[:license_files].collect do |file|
          File.join(bundled_package[:path], file)
        end
        cp(license_files, bundled_package_license_dir)
      end
    end
  end

  def configure(package)
    sh("./autogen.sh") if package.windows.need_autogen?
    sh("autoreconf --install") if package.windows.need_autoreconf?
    sh("./configure",
       cc_env(package),
       "CPPFLAGS=#{cppflags(package)}",
       "LDFLAGS=#{ldflags(package)}",
       "--prefix=#{dist_dir}",
       "--host=#{@package.windows.build_host}",
       *package.windows.configure_args) or exit(false)
  end

  def cmake(package)
    toolchain_cmake_path = "toolchain.cmake"
    File.open(toolchain_cmake_path, "w") do |toolchain|
      toolchain.puts(<<-CMAKE)
SET(CMAKE_SYSTEM_NAME Windows)
SET(MSVC_CXX_ARCHITECTURE_ID #{@package.windows.build_architecture})
SET(CMAKE_SYSTEM_PROCESSOR #{@package.windows.build_architecture})

SET(CMAKE_C_COMPILER #{@package.windows.build_host}-gcc)
SET(CMAKE_CXX_COMPILER #{@package.windows.build_host}-g++)
SET(CMAKE_RC_COMPILER #{@package.windows.build_host}-windres)

SET(CMAKE_FIND_ROOT_PATH #{cmake_root_paths.join(" ")})
      CMAKE
    end
    sh("cmake",
       ".",
       "-DCMAKE_TOOLCHAIN_FILE=#{toolchain_cmake_path}",
       *package.windows.cmake_args) or exit(false)
  end

  def cc_env(package)
    "CC=#{cc(package)}"
  end

  def build_packages
    packages = @package.external_packages.select do |package|
      package.windows.build?
    end
    # For backward compatibility
    packages + @package.windows.build_packages
  end

  def dist_dir
    @package.windows.absolute_binary_dir
  end

  def license_dir
    dist_dir + "share" + "license"
  end

  def glib2_binary_base_dir
    @package.glib2_root_dir + "vendor" + "local"
  end

  def glib2_include_path
    "#{glib2_binary_base_dir}/include"
  end

  def glib2_lib_path
    "#{glib2_binary_base_dir}/lib"
  end

  def rcairo_windows_dir
    suffix = @package.windows.build_architecture_suffix
    @package.project_root_dir.parent + "rcairo.#{suffix}"
  end

  def rcairo_windows_binary_base_dir
    rcairo_windows_dir + "vendor" + "local"
  end

  def rcairo_windows_pkgconfig_path
    "#{rcairo_windows_binary_base_dir}/lib/pkgconfig"
  end

  def rcairo_windows_include_path
    "#{rcairo_windows_binary_base_dir}/include"
  end

  def rcairo_windows_lib_path
    "#{rcairo_windows_binary_base_dir}/lib"
  end

  def cc(package)
    cc_command_line = [
      "#{@package.windows.build_host}-gcc",
      *package.windows.cc_args,
    ]
    cc_command_line.compact.join(" ")
  end

  def cppflags(package)
    include_paths = package.windows.include_paths
    if @package.windows.build_dependencies.include?("glib2")
      include_paths += [glib2_include_path]
    end
    include_paths += [
      rcairo_windows_include_path,
      dist_dir + 'include',
    ]
    cppflags = include_paths.collect do |path|
      "-I#{path}"
    end
    cppflags.join(" ")
  end

  def ldflags(package)
    library_paths = package.windows.library_paths
    if @package.windows.build_dependencies.include?("glib2")
      library_paths += [glib2_lib_path]
    end
    library_paths += [
      rcairo_windows_lib_path,
      dist_dir + 'lib',
    ]
    ldflags = library_paths.collect do |path|
      "-L#{path}"
    end
    ldflags.join(" ")
  end

  def cmake_root_paths
    paths = [
      "/usr/#{@package.windows.build_host}",
      rcairo_windows_binary_base_dir.to_path,
    ]
    @package.windows.build_dependencies.each do |package|
      paths << "#{@package.project_root_dir}/#{package}/vendor/local"
    end
    paths
  end

  def add_gobject_introspection_make_args(common_make_args)
    unless @package.windows.build_dependencies.include?("gobject-introspection")
      return
    end

    dependencies = [
      "gobject-introspection",
      @package.name,
    ]
    dependencies += @package.windows.gobject_introspection_dependencies
    data_dirs = dependencies.collect do |package|
      "#{@package.project_root_dir}/#{package}/vendor/local/share"
    end
    common_make_args << "XDG_DATA_DIRS=#{data_dirs.join(File::PATH_SEPARATOR)}"
  end
end
