# This class implements an environment source based on recieving a hash of
# environments
#
# @since 3.4.0
#
# DESCRIPTION
#
# This class implements environments defined by a hash having the following
# schema:
#
#     ---
#     type: object
#     additionalProperties:
#       type: object
#       properties:
#         type:
#           type: string
#         basedir:
#           type: string
#         modules:
#           type: object
#           additionalProperties:
#             type: object
#         moduledir:
#           type: string
#       additionalProperties:
#         type: string
#
# The top-level keys in the hash are environment names. Keys in individual
# environments should be the same as those which would be given to define a
# single source in r10k.yaml. Additionally, the "modules" key (and moduledir)
# can be used to designate module content for the environment, independent of
# the base source parameters.
#
# Example:
#
#     ---
#     production:
#       type: git
#       remote: 'https://github.com/reidmv/control-repo.git'
#       ref: '1.0.0'
#       modules:
#         geoffwilliams-r_profile: '1.1.0'
#         geoffwilliams-r_role: '2.0.0'
#
#     development:
#       type: git
#       remote: 'https://github.com/reidmv/control-repo.git'
#       ref: 'master'
#       modules:
#         geoffwilliams-r_profile: '1.1.0'
#         geoffwilliams-r_role: '2.0.0'
#
# USAGE
#
# The following is an example implementation class showing how to use the
# R10K::Source::Hash abstract base class. Assume an r10k.yaml file such as:
#
#     ---
#     sources:
#       proof-of-concept:
#         type: demo
#         basedir: '/etc/puppetlabs/code/environments'
#
# Class implementation:
#
#     class R10K::Source::Demo < R10K::Source::Hash
#       R10K::Source.register(:demo, self)
#
#       def initialize(name, basedir, options = {})
#         # This is just a demo class, so we hard-code an example :environments
#         # hash here. In a real class, we might do something here such as
#         # perform an API call to retrieve an :environments hash.
#         options[:environments] = {
#           'production' => {
#             'remote'  => 'https://git.example.com/puppet/control-repo.git',
#             'ref'     => 'release-141',
#             'modules' => {
#               'puppetlabs-stdlib' => '6.1.0',
#               'puppetlabs-ntp' => '8.1.0',
#               'example-myapp1' => {
#                 'git' => 'https://git.example.com/puppet/example-myapp1.git',
#                 'ref' => 'v1.3.0',
#               },
#             },
#           },
#           'development' => {
#             'remote'  => 'https://git.example.com/puppet/control-repo.git',
#             'ref'     => 'master',
#             'modules' => {
#               'puppetlabs-stdlib' => '6.1.0',
#               'puppetlabs-ntp' => '8.1.0',
#               'example-myapp1' => {
#                 'git' => 'https://git.example.com/puppet/example-myapp1.git',
#                 'ref' => 'v1.3.1',
#               },
#             },
#           },
#         }
#
#         # All we need to do is supply options with the :environments hash.
#         # The R10K::Source::Hash parent class takes care of the rest.
#         super(name, basedir, options)
#       end
#     end
#
# Example output:
#
#     [root@master:~] % r10k deploy environment production -pv
#     INFO     -> Using Puppetfile '/etc/puppetlabs/code/environments/production/Puppetfile'
#     INFO     -> Using Puppetfile '/etc/puppetlabs/code/environments/development/Puppetfile'
#     INFO     -> Deploying environment /etc/puppetlabs/code/environments/production
#     INFO     -> Environment production is now at 74ea2e05bba796918e4ff1803018c526337ef5f3
#     INFO     -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/stdlib
#     INFO     -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/ntp
#     INFO     -> Deploying Environment content /etc/puppetlabs/code/environments/production/modules/myapp1
#     INFO     -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/ruby_task_helper
#     INFO     -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/bolt_shim
#     INFO     -> Deploying Puppetfile content /etc/puppetlabs/code/environments/production/modules/apply_helpers
#
class R10K::Source::Hash < R10K::Source::Base

  # @param hash [Hash] A hash to validate.
  # @return [Boolean] False if the hash is obviously invalid. A true return
  #   means _maybe_ it's valid.
  def self.valid_environments_hash?(hash)
    # TODO: more robust schema valiation
    hash.is_a?(Hash)
  end

  # @param name [String] The identifier for this source.
  # @param basedir [String] The base directory where the generated environments will be created.
  # @param options [Hash] An additional set of options for this source. The
  #   semantics of this hash may depend on the source implementation.
  #
  # @option options [Boolean, String] :prefix If a String this becomes the prefix.
  #   If true, will use the source name as the prefix. All sources should respect this option.
  #   Defaults to false for no environment prefix.
  # @option options [Hash] :environments The hash definition of environments
  def initialize(name, basedir, options = {})
    super(name, basedir, options)
  end

  # Set the environment hash for the source. The environment hash is what the
  # source uses to generate enviroments.
  # @param hash [Hash] The hash to sanitize and use as the source's environments.
  #   Should be formatted for use with R10K::Environment#from_hash.
  def set_environments_hash(hash)
    @environments_hash = hash.reduce({}) do |memo,(name,opts)|
      R10K::Util::SymbolizeKeys.symbolize_keys!(opts)
      memo.merge({ 
        name => opts.merge({
          basedir: @basedir,
          dirname: R10K::Environment::Name.new(name, {prefix: @prefix,
                                                      source: @name,
                                                      strip_component: @strip_component}).dirname
        })
      })
    end
  end

  # Return the sanitized environments hash for this source. The environments
  # hash should contain objects formatted for use with R10K::Environment#from_hash.
  # If the hash does not exist it will be built based on @options.
  def environments_hash
    @environments_hash ||= set_environments_hash(@options.fetch(:environments, {}))
  end

  def environments
    @environments ||= environments_hash.map do |name, hash|
      R10K::Environment.from_hash(name, hash.merge({overrides: @options[:overrides]}))
    end
  end

  # List all environments that should exist in the basedir for this source
  # @note This is required by {R10K::Util::Basedir}
  # @return [Array<String>]
  def desired_contents
    environments.map {|env| env.dirname }
  end

end
