# -*- ruby -*-
#encoding: utf-8

require 'monitor'
require 'loggability' unless defined?( Loggability )


# A class which encapsulates the logic and data of temporarily overriding one or
# more aspects of logging for the execution of one or more blocks of code.
#
# It's not meant to be used directly, but via one of the override aggregate methods
# on Loggability:
#
# * Loggability.with_level
# * Loggability.outputting_to
# * Loggability.formatted_with
#
class Loggability::Override
	include MonitorMixin


	### Return an Override with its logging level set to +newlevel+.
	def self::with_level( new_level )
		return self.new( level: new_level )
	end


	### Return an Override with its logging output set to +new_destination+.
	def self::outputting_to( new_destination )
		return self.new( logdev: new_destination )
	end


	### Return an Override with its logging formatter set to +formatter+.
	def self::formatted_with( new_formatter )
		return self.new( formatter: new_formatter )
	end


	### Create a new Override with the specified +settings+ that will be applied
	### during a call to #call, and then reverted when #call returns. Valid +settings+
	### are:
	###
	### [:level]
	###   Set the level of all Loggers to the value.
	### [:logdev]
	###   Set the destination log device of all Loggers to the value.
	### [:formatter]
	###   Set the formatter for all Loggers to the value (a Loggability::Formatter).
	###
	def initialize( settings={} )
		super()

		@settings = settings
		@overridden_settings = {}
	end


	### Copy constructor -- make copies of the internal data structures
	### when duplicated.
	def initialize_copy( original )
		@settings = original.settings.dup
		@overridden_settings = {}
	end


	######
	public
	######

	# The Override's settings Hash (the settings that will be applied during
	# an overridden #call).
	attr_reader :settings

	# The original settings preserved by the Override during a call to #call,
	# keyed by the logger they belong to.
	attr_reader :overridden_settings


	### Call the provided block with configured overrides applied, and then restore
	### the previous settings before control is returned.
	def call
		self.apply_overrides
		yield
	ensure
		self.restore_overridden_settings
	end


	#
	# Mutator Methods
	#

	### Return a clone of the receiving Override with its logging level
	### set to +newlevel+.
	def with_level( new_level )
		return self.clone_with( level: new_level )
	end


	### Return a clone of the receiving Override with its logging output
	### set to +new_destination+.
	def outputting_to( new_destination )
		return self.clone_with( logdev: new_destination )
	end


	### Return a clone of the receiving Override with its logging formatter
	### set to +formatter+.
	def formatted_with( new_formatter )
		return self.clone_with( formatter: new_formatter )
	end


	### Return the object as a human-readable string suitable for debugging.
	def inspect
		return "#<%p:%#016x formatter: %s, level: %s, output: %s>" % [
			self.class,
			self.object_id * 2,
			self.settings[:formatter] || '-',
			self.settings[:level] || '-',
			self.settings[:logdev] ? self.settings[:logdev].class : '-',
		]
	end


	#########
	protected
	#########

	### Return a clone that has been modified with the specified +new_settings+.
	def clone_with( new_settings )
		newobj = self.dup
		newobj.settings.merge!( new_settings )

		return newobj
	end


	### Apply any configured overrides to all loggers.
	def apply_overrides
		self.synchronize do
			raise LocalJumpError, "can't be called re-entrantly" unless
				@overridden_settings.empty?
			@overridden_settings = self.gather_current_settings
		end

		Loggability.log_hosts.each do |key, host|
			host.logger.restore_settings( self.settings )
		end
	end


	### Return a Hash of Loggers with the settings they currently have.
	def gather_current_settings
		return Loggability.log_hosts.values.each_with_object( {} ) do |host, hash|
			hash[ host ] = host.logger.settings
		end
	end


	### Restore the last settings saved by #apply_overrides to their corresponding
	### loggers.
	def restore_overridden_settings
		@overridden_settings.each do |host, settings|
			host.logger.restore_settings( settings )
		end
		@overridden_settings.clear
	end

end # class Loggability::Override