The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package Test::Override::UserAgent::Scope;

use 5.008001;
use strict;
use warnings 'all';

###########################################################################
# METADATA
our $AUTHORITY = 'cpan:DOUGDUDE';
our $VERSION   = '0.004001';

###########################################################################
# MODULE IMPORTS
use Carp qw(croak);
use LWP::Protocol; # Not actually required here, but want it to be loaded
use Scalar::Util;
use Sub::Install 0.90;
use Sub::Override;

###########################################################################
# ALL IMPORTS BEFORE THIS WILL BE ERASED
use namespace::clean 0.04 -except => [qw(meta)];

###########################################################################
# METHODS
sub scheme_implementor {
	my ($self, $scheme) = @_;

	# Lower-case scheme
	$scheme = lc $scheme;

	if (!exists $self->{_protocol_classes}->{$scheme}) {
		# Create a new scheme implementor
		$self->_create_scheme_implementor($scheme);
	}

	# Return the name of the class to use
	return $self->{_protocol_classes}->{$scheme};
}

###########################################################################
# CONSTRUCTOR
sub new {
	my ($class, @args) = @_;

	# Get the arguments as a plain hash
	my %args = @args == 1 ? %{shift @args}
	                      : @args
	                      ;

	# Create a hash with configuration information
	my %data = (
		# Attributes
		override => undef,

		# Private attributes
		_original_implementor_lookup => undef,
		_protocol_classes            => {},
	);

	# Set attributes
	foreach my $arg (grep { m{\A [^_]}msx } keys %data) {
		if (exists $args{$arg}) {
			$data{$arg} = $args{$arg};
		}
	}

	if (!defined $data{override}) {
		croak 'Must supply override attribute';
	}

	# Bless the hash to this class
	my $self = bless \%data, $class;

	# Set our unique name
	$self->{_uniq_name} = $class . '::Number' . Scalar::Util::refaddr($self);

	# Get the current implementor lookup
	$self->{_original_implementor_lookup} = \&LWP::Protocol::implementor;

	# Store the scope override reference
	$self->{_scope_override} = $self->_install_in_scope;

	# Return our blessed configuration
	return $self;
}

###########################################################################
# DESTRUCTOR
sub DESTROY {
	my ($self) = @_;

	# Destroy the override
	undef $self->{_scope_override};

	# Destroy all the created packages
	foreach my $scheme (keys %{$self->{_protocol_classes}}) {
		$self->_destroy_scheme_implementor($scheme);
	}

	return;
}

###########################################################################
# PRIVATE METHODS
sub _create_scheme_implementor {
	my ($self, $scheme) = @_;

	# Calculate a new scheme class name
	my $new_scheme_class = sprintf '%s::%s',
		$self->{_uniq_name}, $scheme;

	# Install new() into the scheme class
	Sub::Install::install_sub({
		into => $new_scheme_class,
		as   => 'new',
		code => $self->_generate_scheme_new,
	});

	# Install request() into the scheme class
	Sub::Install::install_sub({
		into => $new_scheme_class,
		as   => 'request',
		code => $self->_generate_scheme_request($scheme),
	});

	# Save the name of the new class
	$self->{_protocol_classes}->{$scheme} = $new_scheme_class;

	return $new_scheme_class;
}
sub _destroy_scheme_implementor {
	my ($self, $scheme) = @_;

	# Get the package name of the scheme
	my $package = $self->{_protocol_classes}->{$scheme};

	if (defined $package) {
		# Delete new and request methods
		undef &{$package . '::new'};
		undef &{$package . '::request'};
	}

	return;
}
sub _generate_scheme_new {
	my ($self) = @_;

	return sub {
		my ($class, $scheme, $ua) = @_;

		my $object = bless {
			scheme => $scheme,
			ua     => $ua,
		}, $class;

		return $object;
	}
}
sub _generate_scheme_request {
	my ($self, $scheme) = @_;

	# Copy self
	my $weak_self = $self;

	# Weaken the self reference
	Scalar::Util::weaken($weak_self);

	return sub {
		my ($proto_self, $request, $proxy, $arg, $size, $timeout) = @_;

		# Get the override object
		my $override = $weak_self->{override};

		# Process the request by us
		my $response = $override->handle_request(
			$request,
			live_request_handler => sub {
				# Get the normal implementor
				my $implementor_class = $weak_self->{_original_implementor_lookup}->($scheme);

				if (!defined $implementor_class) {
					croak "Protocol scheme '$scheme' is not supported";
				}

				# Create a new instance
				my $implementor = $implementor_class->new($proto_self->{qw(scheme ua)});

				# Make the request
				my $live_response = $implementor->request($request, $proxy, $arg, $size, $timeout);

				return $live_response;
			},
		);

		return $response;
	};
}
sub _install_in_scope {
	my ($self) = @_;

	# Get the current implementor lookup
	my $implementor_lookup = \&LWP::Protocol::implementor;

	# Created a weakened self to allow for destruction
	my $weak_self = $self;
	Scalar::Util::weaken($weak_self);

	# Create an override for the current scope
	my $override = Sub::Override->new(
		'LWP::Protocol::implementor' => sub { return $weak_self->scheme_implementor(shift); },
	);

	return $override;
}

1;

__END__

=head1 NAME

Test::Override::UserAgent::Scope - Scoping the user agent overrides

=head1 VERSION

This documentation refers to version 0.004001

=head1 SYNOPSIS

  # $scope created by Test::Override::UserAgent

  # Say the class name the implements the given scheme
  say $scope->scheme_implementor($scheme);

=head1 DESCRIPTION

This module is a used to specify a scope that L<LWP::UserAgent|LWP::UserAgent>
will be overridden with the specified configuration.

=head1 CONSTRUCTOR

=head2 new

This will construct a new configuration object to allow for configuring user
agent overrides.

=over 4

=item B<new(%attributes)>

C<%attributes> is a HASH where the keys are attributes (specified in the
L</ATTRIBUTES> section).

=item B<new($attributes)>

C<$attributes> is a HASHREF where the keys are attributes (specified in the
L</ATTRIBUTES> section).

=back

=head1 ATTRIBUTES

=head2 override

This is a L<Test::Override::UserAgent|Test::Override::UserAgent> object that
specifies the configuration to use for this override.

=head1 METHODS

=head2 scheme_implementor

This takes the name of a scheme and returns the name of the class that will
implement L<LWP::Protocol|LWP::Protocol> for that scheme.

=head1 DEPENDENCIES

=over 4

=item * L<Carp|Carp>

=item * L<LWP::Protocol|LWP::Protocol>

=item * L<Scalar::Util|Scalar::Util>

=item * L<Sub::Install|Sub::Install> 0.90

=item * L<Sub::Override|Sub::Override>

=item * L<namespace::clean|namespace::clean> 0.04

=back

=head1 AUTHOR

Douglas Christopher Wilson, C<< <doug at somethingdoug.com> >>

=head1 BUGS AND LIMITATIONS

Please report any bugs or feature requests to
C<bug-test-override-useragent at rt.cpan.org>, or through the web interface at
L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Test-Override-UserAgent>. I
will be notified, and then you'll automatically be notified of progress on your
bug as I make changes.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

  perldoc Test::Override::UserAgent::Scope

You can also look for information at:

=over 4

=item * RT: CPAN's request tracker

L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Test-Override-UserAgent>

=item * AnnoCPAN: Annotated CPAN documentation

L<http://annocpan.org/dist/Test-Override-UserAgent>

=item * CPAN Ratings

L<http://cpanratings.perl.org/d/Test-Override-UserAgent>

=item * Search CPAN

L<http://search.cpan.org/dist/Test-Override-UserAgent/>

=back


=head1 LICENSE AND COPYRIGHT

Copyright 2010 Douglas Christopher Wilson.

This program is free software; you can redistribute it and/or
modify it under the terms of either:

=over 4

=item * the GNU General Public License as published by the Free
Software Foundation; either version 1, or (at your option) any
later version, or

=item * the Artistic License version 2.0.

=back