# frozen_string_literal: true

require 'forwardable'
class Hiera
  class Scope
    extend Forwardable

    CALLING_CLASS = 'calling_class'
    CALLING_CLASS_PATH = 'calling_class_path'
    CALLING_MODULE = 'calling_module'
    MODULE_NAME = 'module_name'

    CALLING_KEYS = [CALLING_CLASS, CALLING_CLASS_PATH, CALLING_MODULE].freeze
    EMPTY_STRING = ''

    attr_reader :real

    def initialize(real)
      @real = real
    end

    def [](key)
      case key
      when CALLING_CLASS
        ans = find_hostclass(@real)
      when CALLING_CLASS_PATH
        ans = find_hostclass(@real).gsub(/::/, '/')
      when CALLING_MODULE
        ans = safe_lookupvar(MODULE_NAME)
      else
        ans = safe_lookupvar(key)
      end
      ans == EMPTY_STRING ? nil : ans
    end

    # This method is used to handle the throw of :undefined_variable since when
    # strict variables is not in effect, missing handling of the throw leads to
    # a more expensive code path.
    #
    def safe_lookupvar(key)
      reason = catch :undefined_variable do
        return @real.lookupvar(key)
      end

      case Puppet[:strict]
      when :off
        # do nothing
      when :warning
        Puppet.warn_once(Puppet::Parser::Scope::UNDEFINED_VARIABLES_KIND, _("Variable: %{name}") % { name: key },
                         _("Undefined variable '%{name}'; %{reason}") % { name: key, reason: reason })
      when :error
        raise ArgumentError, _("Undefined variable '%{name}'; %{reason}") % { name: key, reason: reason }
      end
      nil
    end
    private :safe_lookupvar

    def exist?(key)
      CALLING_KEYS.include?(key) || @real.exist?(key)
    end

    def include?(key)
      CALLING_KEYS.include?(key) || @real.include?(key)
    end

    def catalog
      @real.catalog
    end

    def resource
      @real.resource
    end

    def compiler
      @real.compiler
    end

    def find_hostclass(scope)
      if scope.source and scope.source.type == :hostclass
        return scope.source.name.downcase
      elsif scope.parent
        return find_hostclass(scope.parent)
      else
        return nil
      end
    end
    private :find_hostclass

    # This is needed for type conversion to work
    def_delegators :@real, :call_function
  end
end
