class Target
  public

  begin
    include Rake::DSL
  rescue
  end

  def initialize(target)
    @target       = target
    @aliases      = []
    @objects      = []
    @libraries    = []
    @dependencies = []
    @file_deps    = []
    @only_if      = true
    @debug        = {}
    @desc         = nil
    @namespace    = nil
  end

  def debug(category)
    @debug[category] = !@debug[category]
    self
  end

  def debug_do
    yield self
    self
  end

  def description(description)
    @desc = description
    self
  end

  def only_if(condition)
    @only_if = condition
    self
  end

  def end_if
    only_if true
  end

  def extract_options(list)
    options = list.empty? || !list.last.is_a?(Hash) ? {} : list.pop
    return list, options
  end

  def aliases(*list)
    @aliases += list.compact
    self
  end

  def sources(*list)
    list, options = extract_options list

    if @debug[:sources]
      puts "Target::sources: only_if #{@only_if}; list & options:"
      pp list
      pp options
    end

    return self if !@only_if || (options.include?(:if) && !options[:if])

    ext_map = {
      '.ui'  => 'h',
      '.moc' => '.moco',
    }

    obj_re          = /\.(?:moc)?o$/
    no_file_deps_re = /\.o$/

    list           = list.collect { |e| e.respond_to?(:to_a) ? e.to_a : e }.flatten
    file_mode      = (options[:type] || :file) == :file
    except         = !file_mode && options[:except].is_a?(Array) ? options[:except].collect { |file| list.collect { |dir| "#{dir}/#{file}" } }.flatten.to_hash_by : {}
    new_sources    = list.collect { |entry| file_mode ? (entry.respond_to?(:to_a) ? entry.to_a : entry) : FileList["#{entry}/*.c", "#{entry}/*.cpp", "#{entry}/*.cc"].to_a }.flatten.select { |file| !except[file] }.for_target!
    new_deps       = new_sources.collect { |file| [ file.ext(ext_map[ file.pathmap('%x') ] || 'o'), file ] }
    new_file_deps  = new_deps.reject { |src, tgt| no_file_deps_re.match src }
    new_file_deps += new_sources.select { |file| %r{\.moc$}.match file }.map { |file| [ file, file.ext('h') ] }
    @objects       = ( @objects      + new_deps.collect { |a| a.first }.select { |file| obj_re.match file } ).uniq
    @dependencies  = ( @dependencies + new_deps.collect { |a| a.first }                                     ).uniq
    @file_deps     = ( @file_deps    + new_file_deps                                                        ).uniq

    PCH.moc_users(new_deps.select { |dep| /\.moco$/.match dep[0] }.map { |dep| dep[0].ext(".moc") }.to_a)
    self
  end

  def qt_dependencies_and_sources(subdir, options = {})
    ui_files         = FileList["src/#{subdir}/forms/**/*.ui"].to_a
    ui_h_files       = ui_files.collect { |ui| ui.ext 'h' }
    cpp_files        = FileList["src/#{subdir}/**/*.cpp"].to_a.for_target! - (options[:cpp_except] || [])
    h_files          = FileList["src/#{subdir}/**/*.h"].to_a.for_target! - ui_h_files
    content          = read_files(cpp_files + h_files)
    qobject_h_files  = h_files.select { |h| content[h].any? { |line| /\bQ_OBJECT\b/.match line } }

    form_include_re  = %r{^\s* \# \s* include \s+ \" (#{subdir}/forms/[^\"]+) }x
    dep_builder      = lambda do |files|
      extra_dependencies = {}

      files.each do |file_name|
        content[file_name].each do |line|
          next unless form_include_re.match line

          extra_dependencies[file_name] ||= []
          extra_dependencies[file_name]  << "src/#{$1}"
        end
      end

      extra_dependencies
    end

    dep_builder.call(cpp_files).      each { |cpp, ui_hs| file cpp.ext('o')   => ui_hs }
    dep_builder.call(qobject_h_files).each { |h,   ui_hs| file h.  ext('moc') => ui_hs }

    self.
      sources(ui_files).
      sources(qobject_h_files.collect { |h| h.ext 'moc' }).
      sources(cpp_files)
  end

  def dependencies(*list)
    @dependencies += list.flatten.select { |entry| !entry.blank? } if @only_if
    self
  end

  def libraries(*list)
    list, options = extract_options list

    return self if !@only_if || (options.include?(:if) && !options[:if])

    list.flatten!

    @dependencies += list.collect do |entry|
      case entry
      when :mtxcommon   then "src/common/libmtxcommon.#{$libmtxcommon_as_dll ? "dll" : "a"}"
      when :mtxinput    then "src/input/libmtxinput.a"
      when :mtxoutput   then "src/output/libmtxoutput.a"
      when :mtxmerge    then "src/merge/libmtxmerge.a"
      when :mtxinfo     then "src/info/libmtxinfo.a"
      when :mtxextract  then "src/extract/libmtxextract.a"
      when :mtxpropedit then "src/propedit/libmtxpropedit.a"
      when :mtxunittest then "tests/unit/libmtxunittest.a"
      when :avi         then "lib/avilib-0.6.10/libavi.a"
      when :rmff        then "lib/librmff/librmff.a"
      when :mpegparser  then "src/mpegparser/libmpegparser.a"
      when :ebml        then c?("EBML_MATROSKA_INTERNAL") ? "lib/libebml/src/libebml.a"         : nil
      when :matroska    then c?("EBML_MATROSKA_INTERNAL") ? "lib/libmatroska/src/libmatroska.a" : nil
      when :gtest       then $gtest_internal              ? "lib/gtest/src/libgtest.a"          : nil
      when :pugixml     then c?(:PUGIXML_INTERNAL)        ? "lib/pugixml/src/libpugixml.a"      : nil
      when :fmt         then c?(:FMT_INTERNAL)            ? "lib/fmt/src/libfmt.a"              : nil
      else                   nil
      end
    end.compact

    @libraries += list.collect do |entry|
      case entry
      when nil               then nil
      when :magic            then c(:MAGIC_LIBS)
      when :flac             then c(:FLAC_LIBS)
      when :iconv            then c(:ICONV_LIBS)
      when :intl             then c(:LIBINTL_LIBS)
      when :cmark            then c(:CMARK_LIBS)
      when :dvdread          then c(:DVDREAD_LIBS)
      when :pcre2            then c(:PCRE2_LIBS)
      when :pugixml          then c?(:PUGIXML_INTERNAL) ? [ '-Llib/pugixml/src', '-lpugixml' ] : c(:PUGIXML_LIBS)
      when :qt               then c(:QT_LIBS)
      when :static           then c(:LINK_STATICALLY)
      when :stdcppfs         then c(:STDCPPFS_LIBS)
      when :mpegparser       then [ '-Lsrc/mpegparser', '-lmpegparser'  ]
      when :mtxinput         then [ '-Lsrc/input',      '-lmtxinput'    ]
      when :mtxoutput        then [ '-Lsrc/output',     '-lmtxoutput'   ]
      when :mtxmerge         then [ '-Lsrc/merge',      '-lmtxmerge'    ]
      when :mtxextract       then [ '-Lsrc/extract',    '-lmtxextract'  ]
      when :mtxpropedit      then [ '-Lsrc/propedit',   '-lmtxpropedit' ]
      when :mtxunittest      then [ '-Ltests/unit',     '-lmtxunittest' ]
      when :ebml             then c(:EBML_LIBS,     '-lebml')
      when :matroska         then c(:MATROSKA_LIBS, '-lmatroska')
      when :CoreFoundation   then '-framework CoreFoundation'
      when String            then entry
      else                        "-l#{entry}"
      end
    end.compact

    self
  end

  def dump
    %w{aliases sources objects dependencies libraries}.each do |type|
      puts "@#{type}:"
      pp instance_variable_get("@#{type}")
    end
    self
  end

  def create
    definition = Proc.new do
      @file_deps.each do |spec|
        file spec.first => spec.last unless spec.first == spec.last
      end

      @aliases.each_with_index do |name, idx|
        desc @desc if (0 == idx) && !@desc.empty?
        task name => @target
      end
    end

    if @namespace
      namespace @namespace, &definition
    else
      definition.call
    end

    create_specific

    self
  end
end