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

require "open-uri"
require "pathname"
require "tempfile"
require "tmpdir"

module GNOME2
  module Rake
    class WindowsBinaryBuildTask
      include ::Rake::DSL

      def initialize(package)
        @package = package
      end

      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 rcairo_binary_base_dir
        rcairo_dir + "vendor" + "local"
      end

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

      def binary_base_dir(package)
        @package.project_root_dir + package + "vendor" + "local"
      end

      private
      def define_build_tasks
        namespace :build do
          task :prepare

          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

        env = build_env
        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
          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(package, common_make_args)
          common_make_args.concat(package.windows.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
          # build_make_args << "--debug"
          # build_make_args << "V=1"
          # build_make_args << "VERBOSE=1"
          # env["GI_SCANNER_DEBUG"] = "save-temps"
          if File.exist?("meson.build") and not File.exist?("Makefile.am")
            source_dir = Dir.pwd
            build_dir = "build"
            mkdir_p(build_dir)
            Dir.chdir(build_dir) do
              meson(env, package, source_dir)
              sh(env.merge(make_args_to_env(build_make_args)),
                 "nice", "ninja") or exit(false)
              sh(env.merge(make_args_to_env(install_make_args)),
                 "ninja", "install") or exit(false)
            end
          else
            if File.exist?("Makefile.in")
              configure(env, package)
            else
              cmake(env, package)
            end
            sh(env, "nice", "make", *build_make_args) or exit(false)
            sh(env, "make", "install", *install_make_args) or exit(false)
          end

          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 build_env
        path = "#{g_ir_scanner_bin_dir}:#{ENV["PATH"]}"
        env = {
          "GI_HOST_OS" => "nt",
          "MSYSTEM" => "MINGW64",
          "GREP_OPTIONS" => "--text",
          "PATH" => path,
        }
        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_pkgconfig_path
        env
      end

      def configure(env, package)
        sh(env, "./autogen.sh") if package.windows.need_autogen?
        if package.windows.need_autoreconf?
          sh(env, "autoreconf", "--install", "--force")
        end
        sh(env, "env")
        sh(env,
           "./configure",
           cc_env(package),
           dlltool_env,
           "CPPFLAGS=#{cppflags(package)}",
           "LDFLAGS=#{ldflags(package)}",
           "--prefix=#{dist_dir}",
           "--host=#{@package.windows.build_host}",
           # "--target=#{@package.windows.build_host}",
           # "--build=x86_64-pc-linux-gnu",
           *package.windows.configure_args) or exit(false)
        if package.windows.force_to_disable_deplibs_check?
          sh(env,
             "sed",
             "-i",
             "-e", "s/valid_a_lib=false/valid_a_lib=:/g",
             "libtool")
        end
      end

      def meson(env, package, source_dir)
        cross_file = Tempfile.new("meson-cross-file")
        cross_file.puts(<<-CROSS_FILE)
[host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'littel'

[binaries]
c = '/usr/bin/#{cc(package)}'
cpp = '/usr/bin/#{cxx(package)}'
ar = '/usr/bin/#{ar}'
strip = '/usr/bin/#{strip}'
dlltool = '/usr/bin/#{dlltool}'
pkgconfig = '/usr/bin/pkg-config'
        CROSS_FILE
        cross_file.close
        sh(env.merge({
                       "CPPFLAGS" => cppflags(package),
                       "LDFLAGS" => ldflags(package),
                     }),
           "meson",
           source_dir,
           ".",
           "--prefix=#{dist_dir}",
           "--libdir=lib",
           "--cross-file=#{cross_file.path}",
           *package.windows.meson_args) or exit(false)
      end

      def cmake(env, package)
        sh(env,
           "cmake",
           ".",
           "-DCMAKE_INSTALL_PREFIX=#{dist_dir}",
           "-DCMAKE_SYSTEM_NAME=Windows",
           "-DCMAKE_SYSTEM_PROCESSOR=#{@package.windows.build_architecture}",
           "-DCMAKE_C_COMPILER=#{cc(package)}",
           "-DCMAKE_CXX_COMPILER=#{cxx(package)}",
           *package.windows.cmake_args) or exit(false)
      end

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

      def dlltool_env
        "DLLTOOL=#{dlltool}"
      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_include_path
        "#{glib2_binary_base_dir}/include"
      end

      def glib2_lib_path
        "#{glib2_binary_base_dir}/lib"
      end

      def rcairo_dir
        @package.project_root_dir.parent + "rcairo"
      end

      def rcairo_pkgconfig_path
        "#{rcairo_binary_base_dir}/lib/pkgconfig"
      end

      def rcairo_include_path
        "#{rcairo_binary_base_dir}/include"
      end

      def rcairo_lib_path
        "#{rcairo_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 cxx(package)
        cxx_command_line = [
          "#{@package.windows.build_host}-g++",
        ]
        cxx_command_line.compact.join(" ")
      end

      def ar
        "#{@package.windows.build_host}-ar"
      end

      def strip
        "#{@package.windows.build_host}-strip"
      end

      def dlltool
        "#{@package.windows.build_host}-dlltool"
      end

      def g_ir_scanner_bin_dir
        "#{@package.project_root_dir}/gobject-introspection/" +
          "#{@package.native.relative_binary_dir}/bin"
      end

      def g_ir_scanner
        "#{g_ir_scanner_bin_dir}/g-ir-scanner"
      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_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_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_binary_base_dir.to_path,
        ]
        @package.windows.build_dependencies.each do |package|
          paths << binary_base_dir(package).to_path
        end
        paths
      end

      def make_args_to_env(make_args)
        env = {}
        make_args.each do |arg|
          key, value = arg.split("=", 2)
          env[key] = value
        end
        env
      end

      def add_gobject_introspection_make_args(package, common_make_args)
        return unless use_gobject_introspection?(package)

        introspection_scanner = "INTROSPECTION_SCANNER=#{g_ir_scanner}"
        common_make_args << introspection_scanner


        dependencies = [
          "gobject-introspection",
          @package.name,
        ]
        dependencies += @package.windows.gobject_introspection_dependencies

        gi_base_dir = binary_base_dir("gobject-introspection")
        introspection_compiler = "INTROSPECTION_COMPILER="
        introspection_compiler << "#{gi_base_dir}/bin/g-ir-compiler.exe"
        introspection_compiler_args = ""
        dependencies.each do |dependent_package|
          gir_dir = "#{binary_base_dir(dependent_package)}/share/gir-1.0"
          introspection_compiler_args << " --includedir=#{gir_dir}"
        end
        if package.windows.gobject_introspection_compiler_split_args?
          common_make_args << introspection_compiler
          common_make_args <<
            "INTROSPECTION_COMPILER_ARGS=#{introspection_compiler_args}"
          common_make_args <<
            "INTROSPECTION_COMPILER_OPTS=#{introspection_compiler_args}"
        else
          introspection_compiler << " #{introspection_compiler_args}"
          common_make_args << introspection_compiler
        end

        common_make_args << dlltool_env

        data_dirs = dependencies.collect do |dependent_package|
          "#{binary_base_dir(dependent_package)}/share"
        end
        common_make_args << "XDG_DATA_DIRS=#{data_dirs.join(File::PATH_SEPARATOR)}"
      end

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