require 'albacore/app_spec'
require 'albacore/errors/invalid_app_spec_error'
require 'map'

module Albacore
  # An object that is capable of generating FPM commands - use by giving it a
  # spec and then calling #execute or #generate_flags. You may use this object
  # to package a directory.
  #
  class FpmAppSpec
    include ::Albacore::Logging

    # Initialize the object with an ::Albacore::AppSpec
    #
    # @param [::Albacore::AppSpec] app_spec The required package metadata.
    # @param [PathWrap, String] output_dir_path The output path of the rpm/deb
    #                                           package.
    def initialize app_spec, output_dir_path = '.'
      raise ArgumentError, 'missing app_spec parameter' unless app_spec
      @spec = app_spec
      @out  = output_dir_path
    end

    # Generate flags for FPM - if you don't want to execute directly with the object
    # you can use this method to generate what you should give to FPM yourself
    #
    def generate_flags overrides = {}
      { '-s'            => 'dir',
        '-t'            => 'rpm',
        '--name'        => @spec.title,
        '--description' => @spec.description,
        '--url'         => @spec.uri,
        '--category'    => @spec.category,
        '--version'     => @spec.version,
        '--epoch'       => 1,
        '--license'     => @spec.license,
        '-C'            => @spec.dir_path,
        '--depends'     => 'mono',
        '--rpm-digest'  => 'sha256',
        '--package'     => @out
      }.merge(overrides).reject { |_, v| v.nil? }
    end

    # Generates the flags and flatten them to an array that is possible to feed
    # into the #system command
    #
    def generate_flags_flat overrides = {}
      generate_flags(overrides).map { |k, v| [k, v] }.concat(%w|--force .|).flatten
    end

    # gets the filename that the resulting file will have, based on the flags
    # to be passed to fpm
    def filename flags = nil
      flags ||= generate_flags
      # TODO: handle OS architecture properly by taking from context
      "#{flags['--name']}-#{flags['--version']}-#{flags['--epoch']}.x86_64.rpm"
    end

    # Calls FPM with the flags generated
    def generate
      ::Albacore::CrossPlatformCmd.system 'fpm', generate_flags_flat
    end
  end

  class FpmAppSpec::Config
    # create a new configuration for multiple xxproj-s to be packed with fpm into .deb/.rpm
    def initialize
      @bundler = true
      @files   = []
      @out     = '.'
    end

    # turn off the using of bundler; bundler will be used by default
    def no_bundler
      @bundler = false
    end

    # set the output path, defaults to '.'
    def out= out
      @out = out
    end

    # give the configuration a list of files to match
    def files= files
      @files = files
    end

    def opts
      Map.new bundler: @bundler,
              files: @files,
              out: @out
    end
  end

  # task implementation that can be #execute'd
  class FpmAppSpec::Task
    include ::Albacore::Logging
    include ::Albacore::CrossPlatformCmd

    # create a new task instance with the given opts
    def initialize opts
      raise ArgumentError, 'opts is nil' if opts.nil?
      @opts = opts
    end

    # this runs fpm and does some file copying
    def execute
      warn 'executing fpm app spec task, but there are no input files [fpm_app_spec::task#execute]' if
        @opts.get(:files).empty?

      fpm_package @opts.get(:out), @opts.get(:files)
    end

    private
    def fpm_package out, appspecs
      pkg = File.join out, 'pkg'

      appspecs.
        map { |path| Albacore::AppSpec.load path }.
        map { |spec| [spec, Albacore::FpmAppSpec.new(spec, pkg)] }.
        each do |spec, fpm|
        targ = "#{out}/#{spec.title}/tmp-dest/"
        FileUtils.mkdir_p targ

        bin = File.join targ, "opt/#{spec.title}/bin"
        FileUtils.mkdir_p bin
        FileUtils.cp_r Dir.glob(File.join(fpm_rel(spec, spec.bin_folder), '*')),
                       bin, verbose: true

        etc = File.join targ, "etc/#{spec.title}"
        FileUtils.mkdir_p etc, verbose: true
  #      FileUtils.cp_r Dir.glob(File.join(fpm_rel(spec, spec.conf_folder), '*')),
  #                     etc, verbose: true

        spec.contents.each do |con|
          FileUtils.cp_r fpm_rel(spec, con), File.join(targ, con), verbose: true
        end

        run fpm.generate_flags_flat({ '-C' => targ })
      end
    end


    def fpm_rel spec, path
      File.join spec.dir_path, path
    end

    def run pars
      if @opts.get :bundle
        system 'bundle', %w|exec fpm|.concat(pars)
      else
        system 'fpm', pars
      end
      Albacore.publish :artifact, OpenStruct.new({ :location => "#{pkg}/#{fpm.filename}" })
    end
  end
end
