#
# Copyright 2014-2018 Chef Software, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

module Omnibus
  class Publisher
    include Digestable

    class << self
      #
      # Shortcut class method for creating a new instance of this class and
      # executing the publishing sequence.
      #
      # @param (see Publisher#initialize)
      #
      def publish(pattern, options = {}, &block)
        new(pattern, options).publish(&block)
      end
    end

    include Logging

    #
    # Create a new publisher from the given pattern.
    #
    # @param [String] pattern
    #   the path/pattern of the release artifact(s)
    #
    # @param [Hash] options
    #   the list of options passed to the publisher
    # @option options [Hash] :platform_mappings A simple
    #   mapping of build to publish platform(s)
    # @example
    #   {
    #     'ubuntu-10.04-x86_64' => [
    #       'ubuntu-10.04-x86_64',
    #       'ubuntu-12.04-x86_64',
    #       'ubuntu-14.04-x86_64',
    #     ],
    #   }
    #
    def initialize(pattern, options = {})
      @pattern = pattern
      @options = options.dup

      if @options[:platform_mappings]
        log.info(log_key) do
          "Publishing will be performed using provided platform mappings."
        end
      end
    end

    #
    # The list of packages that match the pattern in the initializer.
    #
    # @return [Array<String>]
    #
    def packages
      @packages ||= begin
        publish_packages = []
        build_packages   = FileSyncer.glob(@pattern).map { |path| Package.new(path) }

        if @options[:platform_mappings]
          # the platform map is a simple hash with publish to build platform mappings
          @options[:platform_mappings].each_pair do |build_platform, publish_platforms|
            # Splits `ubuntu-12.04-x86_64` into `ubuntu`, `12.04` and `x86_64`
            build_platform, build_platform_version, build_architecture = build_platform.split("-")

            # locate the package for the build platform
            packages = build_packages.select do |p|
              p.metadata[:platform] == build_platform &&
                p.metadata[:platform_version] == build_platform_version &&
                p.metadata[:arch] == build_architecture
            end

            if packages.empty?
              log.warn(log_key) do
                "Could not locate a package for build platform #{build_platform}-#{build_platform_version}-#{build_architecture}. " \
                "Publishing will be skipped for: #{publish_platforms.join(", ")}"
              end
            end

            publish_platforms.each do |publish_platform|
              publish_platform, publish_platform_version, publish_architecture = publish_platform.split("-")

              packages.each do |p|
                # create a copy of our package before mucking with its metadata
                publish_package  = p.dup
                publish_metadata = p.metadata.dup.to_hash

                # override the platform, platform version and architecture in the metadata
                publish_metadata[:platform]         = publish_platform
                publish_metadata[:platform_version] = publish_platform_version
                publish_metadata[:arch]             = publish_architecture

                # Set the updated metadata on the package object
                publish_package.metadata = Metadata.new(publish_package, publish_metadata)

                publish_packages << publish_package
              end
            end
          end
        else
          publish_packages.concat(build_packages)
        end

        if publish_packages.empty?
          log.info(log_key) { "No packages found, skipping publish" }
        end

        publish_packages
      end
    end

    #
    # @abstract
    #
    # @param [Proc] _block
    #   if given, the block will yield the currently uploading "thing"
    #
    # @return [Array<String>]
    #   the list of uploaded packages
    #
    def publish(&_block)
      raise NotImplementedError
    end

    private

    def safe_require(name)
      require name
    rescue LoadError
      raise GemNotInstalled.new(name)
    end
  end
end
