The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
package WWW::SmugMug::API;

use warnings;
use strict;

=head1 NAME

WWW::SmugMug::API - Perl wrapper for the SmugMug API

=head1 VERSION

Version 1.02

=cut

our $VERSION = '1.03';

=head1 SYNOPSIS

    use WWW::SmugMug::API;

    my $sm_api = WWW::SmugMug::API->new(
        {
            sm_api_key => 'changeme',
            secure     => 1,
        }
    );

    $sm_api->login_withPassword(
        {
            EmailAddress => 'me@privacy.net',
            Password     => 'password',
        }
    );
    ...
    $sm_api->logout;
	
=head1 DESCRIPTION

Provides a low-level wrapper for the 1.2.0 version of the SmugMug API.  Calls 
requiring a SessionID use the one returned from the last successful login call
(login_anonymously, login_withPassword, or login_withHash), and calls requiring
the API key use the sm_api_key parameter passed to the constructor; these 
provided parameters cannot be overridden.  All other required parameters must 
be provided by the caller.

The wrapper will silently discard any parameters that it does not recognise.

Method names are the API methods with the 'smugmug.' prefix removed and '_' 
substituted for '.' (e.g. 'smugmug.login.withPassword becomes 
login_withPassword())

Methods return hash references.  The value of the 'stat' key will be 'ok' if
the call succeeded, 'fail' otherwise.  Fail responses contain three key/value
pairs: 'stat', 'message', and 'code'.  Message is an informative message 
about the failure.  The code will be positive for an error thrown by SmugMug
and negative for one from the wrapper.  The following error codes are known:

=over

=item -3

Session not initialised.  Before calling any other methods, you must 
successfully call login_anonymously, login_withPassword, or login_withHash.

=item -2

HTTP error.  Message will be the complete error (e.g. '404 Not Found')

=item -1

Missing required parameter.  All parameters listed in the documentation as
required must be provided, or the wrapper will reject the call.

=item 1

Invalid login.

=item 3

Invalid session.

=item 4

Invalid user.

=item 5

System error.

=item 6

Wrong format.  (Image format when uploading, I believe)

=item 9

Invalid album.

=item 11

Ancient version.

=item 15

Empty set.

=item 18

Invalid API key.

=back 

NOTE: While this wrapper does provide a method called images_upload, per the 
best practices recommendation of the SmugMug developers we use a binary 
uploading method (HTTP PUT) to upload the image; it is not an implementation
of the API method smugmug.images.upload.  images_uploadFromURL is not provided.

=cut

use Carp;
use LWP::UserAgent;
use JSON;

my $KEEP_ALIVE_CACHESIZE = 10;

my $API_VERSION   = '1.2.1';
my $ENDPOINT_BASE = 'api.smugmug.com/services/api/json/1.2.1/';

my $ERROR_CODE = {
	MISSING_REQUIRED_PARAMETER => -1,
	HTTP_ERROR                 => -2,
	SESSION_NOT_INITIALISED    => -3,
};

=head1 SUBROUTINES/METHODS

=head2 new

Create a new client object.  Parameters:

=over

=item sm_api_key

API key

=item agent

Useragent string.  

=item secure

Set this to C<1> to use SSL.  Defaults to C<0>

=item timeout

Time (in seconds) to wait for a response.  Defaults to 30.

=item retry

Set this to C<1> to retry on error.  Defaults to C<0>

=back

=cut

sub new {
	my ( $class, $cnf ) = @_;

	my $retry = $cnf->{retry};
	$retry = 0 unless defined $retry;

	my $agent = $cnf->{agent};
	$agent = "WWW::SmugMug::API/$VERSION" unless defined $agent;

	my $sm_api_key = $cnf->{sm_api_key};
	croak 'No API Key' unless defined $sm_api_key;

	my $sm_endpoint;
	my $secure = $cnf->{secure};
	if ($secure) {
		$sm_endpoint = 'https://' . $ENDPOINT_BASE;
	}
	else {
		$sm_endpoint = 'http://' . $ENDPOINT_BASE;
	}

	my $ua;

	$ua = LWP::UserAgent->new(
		keep_alive            => $KEEP_ALIVE_CACHESIZE,
		requests_redirectable => [qw(GET HEAD DELETE PUT)],
	);

	$ua->agent($agent);
	$ua->timeout(30);
	$ua->env_proxy;

	my $json = JSON->new();

	my $self = bless {
		ua          => $ua,
		json        => $json,
		retry       => $retry,
		agent       => $agent,
		sm_api_key  => $sm_api_key,
		sm_endpoint => $sm_endpoint,
		sm_session  => undef,
	}, $class;

	return $self;
}

#BEGIN autogenerated subs

=head2 albums_applyWatermark

Calls smugmug.albums.applyWatermark.

Parameters:

=over

=item AlbumID (integer) (required)

=item WatermarkID (integer) (required)

=back

=cut

sub albums_applyWatermark {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.applyWatermark';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID WatermarkID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_changeSettings

Calls smugmug.albums.changeSettings.

Parameters:

=over

=item AlbumID (integer) (required)

=item Title (string)

=item CategoryID (integer)

=item SubCategoryID (integer)

=item Description (string)

=item Keywords (string)

=item AlbumTemplateID (integer)

=item Geography (boolean)

=item HighlightID (integer)

=item Position (integer)

=item Header (boolean)

=item Clean (boolean)

=item EXIF (boolean)

=item Filenames (boolean)

=item SquareThumbs (boolean)

=item TemplateID (integer)

=item SortMethod (string)

=item SortDirection (boolean)

=item Password (string)

=item PasswordHint (string)

=item Public (boolean)

=item WorldSearchable (boolean)

=item SmugSearchable (boolean)

=item External (boolean)

=item Protected (boolean)

=item Watermarking (boolean)

=item WatermarkID (integer)

=item HideOwner (boolean)

=item Larges (boolean)

=item XLarges (boolean)

=item X2Larges (boolean)

=item X3Larges (boolean)

=item Originals (boolean)

=item CanRank (boolean)

=item FriendEdit (boolean)

=item FamilyEdit (boolean)

=item Comments (boolean)

=item Share (boolean)

=item Printable (boolean)

=item DefaultColor (boolean)

=item ProofDays (integer)

=item Backprinting (string)

=item UnsharpAmount (float)

=item UnsharpRadius (float)

=item UnsharpThreshold (float)

=item UnsharpSigma (float)

=item CommunityID (integer)

=back

=cut

sub albums_changeSettings {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.changeSettings';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Title CategoryID SubCategoryID Description Keywords AlbumTemplateID Geography HighlightID Position Header Clean EXIF Filenames SquareThumbs TemplateID SortMethod SortDirection Password PasswordHint Public WorldSearchable SmugSearchable External Protected Watermarking WatermarkID HideOwner Larges XLarges X2Larges X3Larges Originals CanRank FriendEdit FamilyEdit Comments Share Printable DefaultColor ProofDays Backprinting UnsharpAmount UnsharpRadius UnsharpThreshold UnsharpSigma CommunityID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_create

Calls smugmug.albums.create.

Parameters:

=over

=item Title (string) (required)

=item CategoryID (string) (required)

=item SubCategoryID (integer)

=item Description (string)

=item Keywords (string)

=item AlbumTemplateID (integer)

=item Geography (boolean)

=item HighlightID (integer)

=item Position (integer)

=item Header (boolean)

=item Clean (boolean)

=item EXIF (boolean)

=item Filenames (boolean)

=item SquareThumbs (boolean)

=item TemplateID (integer)

=item SortMethod (string)

=item SortDirection (boolean)

=item Password (string)

=item PasswordHint (string)

=item Public (boolean)

=item WorldSearchable (boolean)

=item SmugSearchable (boolean)

=item External (boolean)

=item Protected (boolean)

=item Watermarking (boolean)

=item WatermarkID (integer)

=item HideOwner (boolean)

=item Larges (boolean)

=item XLarges (boolean)

=item X2Larges (boolean)

=item X3Larges (boolean)

=item Originals (boolean)

=item CanRank (boolean)

=item FriendEdit (boolean)

=item FamilyEdit (boolean)

=item Comments (boolean)

=item Share (boolean)

=item Printable (boolean)

=item DefaultColor (boolean)

=item ProofDays (integer)

=item Backprinting (string)

=item UnsharpAmount (float)

=item UnsharpRadius (float)

=item UnsharpThreshold (float)

=item UnsharpSigma (float)

=item CommunityID (integer)

=back

=cut

sub albums_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/Title CategoryID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/SubCategoryID Description Keywords AlbumTemplateID Geography HighlightID Position Header Clean EXIF Filenames SquareThumbs TemplateID SortMethod SortDirection Password PasswordHint Public WorldSearchable SmugSearchable External Protected Watermarking WatermarkID HideOwner Larges XLarges X2Larges X3Larges Originals CanRank FriendEdit FamilyEdit Comments Share Printable DefaultColor ProofDays Backprinting UnsharpAmount UnsharpRadius UnsharpThreshold UnsharpSigma CommunityID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_delete

Calls smugmug.albums.delete.

Parameters:

=over

=item AlbumID (integer) (required)

=back

=cut

sub albums_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_get

Calls smugmug.albums.get.

Parameters:

=over

=item NickName (string)

=item Heavy (boolean)

=item SitePassword (string)

=item ShareGroup ()

=back

=cut

sub albums_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/NickName Heavy SitePassword ShareGroup /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_getInfo

Calls smugmug.albums.getInfo.

Parameters:

=over

=item AlbumID (string) (required)

=item Password (string)

=item SitePassword (string)

=item AlbumKey (string)

=back

=cut

sub albums_getInfo {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.getInfo';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Password SitePassword AlbumKey /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_getStats

Calls smugmug.albums.getStats.

Parameters:

=over

=item AlbumID (integer) (required)

=item Month (integer) (required)

=item Year (integer) (required)

=item Heavy (boolean)

=back

=cut

sub albums_getStats {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.getStats';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID Month Year /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Heavy /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_removeWatermark

Calls smugmug.albums.removeWatermark.

Parameters:

=over

=item AlbumID (integer) (required)

=back

=cut

sub albums_removeWatermark {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.removeWatermark';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 albums_reSort

Calls smugmug.albums.reSort.

Parameters:

=over

=item AlbumID (integer) (required)

=item By (string) (required)

=item Direction (string) (required)

=back

=cut

sub albums_reSort {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albums.reSort';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID By Direction /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 albumtemplates_changeSettings

Calls smugmug.albumtemplates.changeSettings.

Parameters:

=over

=item AlbumTemplateID (integer) (required)

=item Name (string)

=item HighlightID (integer)

=item SortMethod (string)

=item SortDirection (boolean)

=item Public (boolean)

=item Password (string)

=item PasswordHint (string)

=item Printable (boolean)

=item Filenames (boolean)

=item Comments (boolean)

=item External (boolean)

=item Originals (boolean)

=item EXIF (boolean)

=item Share (boolean)

=item Header (boolean)

=item Larges (boolean)

=item XLarges (boolean)

=item X2Larges (boolean)

=item X3Larges (boolean)

=item Clean (boolean)

=item Protected (boolean)

=item Watermarking (boolean)

=item FamilyEdit (boolean)

=item FriendEdit (boolean)

=item HideOwner (boolean)

=item DefaultColor (boolean)

=item Geography (boolean)

=item CanRank (boolean)

=item ProofDays (integer)

=item Backprinting (string)

=item SmugSearchable (boolean)

=item UnsharpAmount (float)

=item UnsharpRadius (float)

=item UnsharpThreshold (float)

=item UnsharpSigma (float)

=item WorldSearchable (boolean)

=item TemplateID (integer)

=item CommunityID (integer)

=item WatermarkID (integer)

=back

=cut

sub albumtemplates_changeSettings {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albumtemplates.changeSettings';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumTemplateID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Name HighlightID SortMethod SortDirection Public Password PasswordHint Printable Filenames Comments External Originals EXIF Share Header Larges XLarges X2Larges X3Larges Clean Protected Watermarking FamilyEdit FriendEdit HideOwner DefaultColor Geography CanRank ProofDays Backprinting SmugSearchable UnsharpAmount UnsharpRadius UnsharpThreshold UnsharpSigma WorldSearchable TemplateID CommunityID WatermarkID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albumtemplates_create

Calls smugmug.albumtemplates.create.

Parameters:

=over

=item Name (string)

=item HighlightID (integer)

=item SortMethod (string)

=item SortDirection (boolean)

=item Public (boolean)

=item Password (string)

=item PasswordHint (string)

=item Printable (boolean)

=item Filenames (boolean)

=item Comments (boolean)

=item External (boolean)

=item Originals (boolean)

=item EXIF (boolean)

=item Share (boolean)

=item Header (boolean)

=item Larges (boolean)

=item XLarges (boolean)

=item X2Larges (boolean)

=item X3Larges (boolean)

=item Clean (boolean)

=item Protected (boolean)

=item Watermarking (boolean)

=item FamilyEdit (boolean)

=item FriendEdit (boolean)

=item HideOwner (boolean)

=item DefaultColor (boolean)

=item Geography (boolean)

=item CanRank (boolean)

=item ProofDays (integer)

=item Backprinting (string)

=item SmugSearchable (boolean)

=item UnsharpAmount (float)

=item UnsharpRadius (float)

=item UnsharpThreshold (float)

=item UnsharpSigma (float)

=item WorldSearchable (boolean)

=item TemplateID (integer)

=item CommunityID (integer)

=item WatermarkID (integer)

=back

=cut

sub albumtemplates_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albumtemplates.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/Name HighlightID SortMethod SortDirection Public Password PasswordHint Printable Filenames Comments External Originals EXIF Share Header Larges XLarges X2Larges X3Larges Clean Protected Watermarking FamilyEdit FriendEdit HideOwner DefaultColor Geography CanRank ProofDays Backprinting SmugSearchable UnsharpAmount UnsharpRadius UnsharpThreshold UnsharpSigma WorldSearchable TemplateID CommunityID WatermarkID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 albumtemplates_get

Calls smugmug.albumtemplates.get.

Parameters:

=over

No parameters

=back

=cut

sub albumtemplates_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.albumtemplates.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 categories_create

Calls smugmug.categories.create.

Parameters:

=over

=item Name (string) (required)

=back

=cut

sub categories_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.categories.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/Name /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 categories_delete

Calls smugmug.categories.delete.

Parameters:

=over

=item CategoryID (integer) (required)

=back

=cut

sub categories_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.categories.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/CategoryID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 categories_get

Calls smugmug.categories.get.

Parameters:

=over

=item NickName (string)

=item SitePassword (string)

=back

=cut

sub categories_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.categories.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/NickName SitePassword /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 categories_rename

Calls smugmug.categories.rename.

Parameters:

=over

=item CategoryID (integer) (required)

=item Name (string) (required)

=back

=cut

sub categories_rename {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.categories.rename';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/CategoryID Name /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 family_add

Calls smugmug.family.add.

Parameters:

=over

=item NickName (string) (required)

=back

=cut

sub family_add {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.family.add';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/NickName /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 family_get

Calls smugmug.family.get.

Parameters:

=over

No parameters

=back

=cut

sub family_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.family.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 family_remove

Calls smugmug.family.remove.

Parameters:

=over

=item NickName (string) (required)

=back

=cut

sub family_remove {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.family.remove';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/NickName /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 family_removeAll

Calls smugmug.family.removeAll.

Parameters:

=over

No parameters

=back

=cut

sub family_removeAll {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.family.removeAll';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 friends_add

Calls smugmug.friends.add.

Parameters:

=over

=item NickName (string) (required)

=back

=cut

sub friends_add {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.friends.add';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/NickName /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 friends_get

Calls smugmug.friends.get.

Parameters:

=over

No parameters

=back

=cut

sub friends_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.friends.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 friends_remove

Calls smugmug.friends.remove.

Parameters:

=over

=item NickName (string) (required)

=back

=cut

sub friends_remove {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.friends.remove';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/NickName /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 friends_removeAll

Calls smugmug.friends.removeAll.

Parameters:

=over

No parameters

=back

=cut

sub friends_removeAll {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.friends.removeAll';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 images_applyWatermark

Calls smugmug.images.applyWatermark.

Parameters:

=over

=item ImageID (integer) (required)

=item WatermarkID (integer) (required)

=back

=cut

sub images_applyWatermark {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.applyWatermark';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID WatermarkID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_changePosition

Calls smugmug.images.changePosition.

Parameters:

=over

=item ImageID (integer) (required)

=item Position (integer) (required)

=back

=cut

sub images_changePosition {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.changePosition';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID Position /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_changeSettings

Calls smugmug.images.changeSettings.

Parameters:

=over

=item ImageID (integer) (required)

=item AlbumID (integer)

=item Caption (string)

=item Keywords (string)

=item Hidden (boolean)

=back

=cut

sub images_changeSettings {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.changeSettings';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/AlbumID Caption Keywords Hidden /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_crop

Calls smugmug.images.crop.

Parameters:

=over

=item ImageID (integer) (required)

=item Width (integer) (required)

=item Height (integer) (required)

=item X (integer) (required)

=item Y (integer) (required)

=back

=cut

sub images_crop {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.crop';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID Width Height X Y /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_delete

Calls smugmug.images.delete.

Parameters:

=over

=item ImageID (integer) (required)

=back

=cut

sub images_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_get

Calls smugmug.images.get.

Parameters:

=over

=item AlbumID (integer) (required)

=item Heavy (boolean)

=item Password (string)

=item SitePassword (string)

=item AlbumKey (string) (required)

=back

=cut

sub images_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID AlbumKey /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Heavy Password SitePassword /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_getEXIF

Calls smugmug.images.getEXIF.

Parameters:

=over

=item ImageID (integer) (required)

=item Password (string)

=item SitePassword (string)

=item ImageKey (string)

=back

=cut

sub images_getEXIF {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.getEXIF';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Password SitePassword ImageKey /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_getInfo

Calls smugmug.images.getInfo.

Parameters:

=over

=item ImageID (integer) (required)

=item Password (string)

=item SitePassword (string)

=item ImageKey (string)

=back

=cut

sub images_getInfo {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.getInfo';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Password SitePassword ImageKey /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_getStats

Calls smugmug.images.getStats.

Parameters:

=over

=item ImageID (integer) (required)

=item Month (integer) (required)

=back

=cut

sub images_getStats {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.getStats';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID Month /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_getURLs

Calls smugmug.images.getURLs.

Parameters:

=over

=item ImageID (integer) (required)

=item TemplateID (integer)

=item Password (string)

=item SitePassword (string)

=item ImageKey (string)

=back

=cut

sub images_getURLs {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.getURLs';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/TemplateID Password SitePassword ImageKey /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_removeWatermark

Calls smugmug.images.removeWatermark.

Parameters:

=over

=item ImageID (integer) (required)

=back

=cut

sub images_removeWatermark {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.removeWatermark';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 images_rotate

Calls smugmug.images.rotate.

Parameters:

=over

=item ImageID (integer) (required)

=item Degrees (integer)

=item Flip (boolean)

=back

=cut

sub images_rotate {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.rotate';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Degrees Flip /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 images_zoomThumbnail

Calls smugmug.images.zoomThumbnail.

Parameters:

=over

=item ImageID (integer) (required)

=item Width (integer) (required)

=item Height (integer) (required)

=item X (integer) (required)

=item Y (integer) (required)

=back

=cut

sub images_zoomThumbnail {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.images.zoomThumbnail';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID Width Height X Y /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 login_anonymously

Calls smugmug.login.anonymously.

Parameters:

=over

No parameters

=back

=cut

sub login_anonymously {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.login.anonymously';
	my $params;

	$params->{APIKey} = $self->{sm_api_key};

	my $result = $self->_get_request($api_method, $params);
	if ($result->{stat} eq 'ok') {
		$self->{sm_session} = $result->{Login}->{Session}->{id};
	}

	return $result;
}
	
=head2 login_withHash

Calls smugmug.login.withHash.

Parameters:

=over

=item UserID (integer) (required)

=item PasswordHash (string) (required)

=back

=cut

sub login_withHash {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.login.withHash';
	my $params;

	$params->{APIKey} = $self->{sm_api_key};

	my @mandatory_params = qw/UserID PasswordHash /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my $result = $self->_get_request($api_method, $params);
	if ($result->{stat} eq 'ok') {
		$self->{sm_session} = $result->{Login}->{Session}->{id};
	}

	return $result;
}
	
=head2 login_withPassword

Calls smugmug.login.withPassword.

Parameters:

=over

=item EmailAddress (string) (required)

=item Password (string) (required)

=back

=cut

sub login_withPassword {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.login.withPassword';
	my $params;

	$params->{APIKey} = $self->{sm_api_key};

	my @mandatory_params = qw/EmailAddress Password /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my $result = $self->_get_request($api_method, $params);
	if ($result->{stat} eq 'ok') {
		$self->{sm_session} = $result->{Login}->{Session}->{id};
	}

	return $result;
}
	
=head2 logout

Calls smugmug.logout.

Parameters:

=over

No parameters

=back

=cut

sub logout {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.logout';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 products_get

Calls smugmug.products.get.

Parameters:

=over

No parameters

=back

=cut

sub products_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.products.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 propricing_getPortfolio

Calls smugmug.propricing.getPortfolio.

Parameters:

=over

=item ProductType (string)

=item ProductID (integer)

=back

=cut

sub propricing_getPortfolio {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.propricing.getPortfolio';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/ProductType ProductID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 propricing_getAlbum

Calls smugmug.propricing.getAlbum.

Parameters:

=over

=item AlbumID (integer) (required)

=item ProductType (string)

=item ProductID (integer)

=back

=cut

sub propricing_getAlbum {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.propricing.getAlbum';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/ProductType ProductID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 propricing_getImage

Calls smugmug.propricing.getImage.

Parameters:

=over

=item ImageID (integer) (required)

=item ProductType (string)

=item ProductID (integer)

=back

=cut

sub propricing_getImage {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.propricing.getImage';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/ProductType ProductID /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_addAlbum

Calls smugmug.sharegroups.addAlbum.

Parameters:

=over

=item ShareGroupID (integer) (required)

=item AlbumID (integer) (required)

=back

=cut

sub sharegroups_addAlbum {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.addAlbum';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ShareGroupID AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_create

Calls smugmug.sharegroups.create.

Parameters:

=over

=item Name (string) (required)

=item Description (string)

=back

=cut

sub sharegroups_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/Name /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Description /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_delete

Calls smugmug.sharegroups.delete.

Parameters:

=over

=item ShareGroupID (integer) (required)

=back

=cut

sub sharegroups_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ShareGroupID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_get

Calls smugmug.sharegroups.get.

Parameters:

=over

=item Heavy (boolean)

=back

=cut

sub sharegroups_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/Heavy /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_getInfo

Calls smugmug.sharegroups.getInfo.

Parameters:

=over

=item ShareGroupID (integer) (required)

=back

=cut

sub sharegroups_getInfo {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.getInfo';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ShareGroupID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 sharegroups_removeAlbum

Calls smugmug.sharegroups.removeAlbum.

Parameters:

=over

=item ShareGroupID (integer) (required)

=item AlbumID (integer) (required)

=back

=cut

sub sharegroups_removeAlbum {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.sharegroups.removeAlbum';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ShareGroupID AlbumID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 subcategories_create

Calls smugmug.subcategories.create.

Parameters:

=over

=item Name (string) (required)

=item CategoryID (integer) (required)

=back

=cut

sub subcategories_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.subcategories.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/Name CategoryID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 subcategories_delete

Calls smugmug.subcategories.delete.

Parameters:

=over

=item SubCategoryID (integer) (required)

=back

=cut

sub subcategories_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.subcategories.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/SubCategoryID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 subcategories_get

Calls smugmug.subcategories.get.

Parameters:

=over

=item CategoryID (integer) (required)

=item NickName (string)

=item SitePassword (string)

=back

=cut

sub subcategories_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.subcategories.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/CategoryID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/NickName SitePassword /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 subcategories_getAll

Calls smugmug.subcategories.getAll.

Parameters:

=over

=item NickName (string)

=item SitePassword (string)

=back

=cut

sub subcategories_getAll {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.subcategories.getAll';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/NickName SitePassword /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 subcategories_rename

Calls smugmug.subcategories.rename.

Parameters:

=over

=item SubCategoryID (integer) (required)

=item Name (string) (required)

=back

=cut

sub subcategories_rename {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.subcategories.rename';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/SubCategoryID Name /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 themes_get

Calls smugmug.themes.get.

Parameters:

=over

No parameters

=back

=cut

sub themes_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.themes.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	return $self->_get_request($api_method, $params);
}

=head2 users_getDisplayName

Calls smugmug.users.getDisplayName.

Parameters:

=over

=item NickName (string)

=back

=cut

sub users_getDisplayName {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.users.getDisplayName';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/NickName /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 users_getTransferStats

Calls smugmug.users.getTransferStats.

Parameters:

=over

=item Month (integer) (required)

=item Year (integer) (required)

=item Heavy (boolean)

=back

=cut

sub users_getTransferStats {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.users.getTransferStats';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/Month Year /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Heavy /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 users_getTree

Calls smugmug.users.getTree.

Parameters:

=over

=item NickName (string)

=item Heavy (boolean)

=item SitePassword (string)

=item Sharegroup ()

=back

=cut

sub users_getTree {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.users.getTree';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/NickName Heavy SitePassword Sharegroup /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 watermarks_changeSettings

Calls smugmug.watermarks.changeSettings.

Parameters:

=over

=item ImageID (integer) (required)

=item Name (string)

=item Pinned (string)

=item Dissolved (integer)

=item Thumbs (boolean)

=back

=cut

sub watermarks_changeSettings {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.watermarks.changeSettings';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Name Pinned Dissolved Thumbs /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 watermarks_create

Calls smugmug.watermarks.create.

Parameters:

=over

=item ImageID (integer) (required)

=item Name (string)

=item Pinned (string)

=item Dissolved (integer)

=item Thumbs (boolean)

=back

=cut

sub watermarks_create {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.watermarks.create';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/ImageID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	my @optional_params = qw/Name Pinned Dissolved Thumbs /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 watermarks_delete

Calls smugmug.watermarks.delete.

Parameters:

=over

=item WatermarkID (integer) (required)

=back

=cut

sub watermarks_delete {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.watermarks.delete';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/WatermarkID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

=head2 watermarks_get

Calls smugmug.watermarks.get.

Parameters:

=over

=item Heavy (boolean)

=back

=cut

sub watermarks_get {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.watermarks.get';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @optional_params = qw/Heavy /;
	for my $param (@optional_params) {
		if (defined $passed_params->{$param}) {
			$params->{$param} = $passed_params->{$param};
		}
	} 
	
	return $self->_get_request($api_method, $params);
}

=head2 watermarks_getInfo

Calls smugmug.watermarks.getInfo.

Parameters:

=over

=item WatermarkID (integer) (required)

=back

=cut

sub watermarks_getInfo {
	my ( $self, $passed_params ) = @_;
	my $api_method = 'smugmug.watermarks.getInfo';
	my $params;

	if(defined $self->{sm_session}) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error('Please call one of the login* methods first.', $ERROR_CODE->{SESSION_NOT_INITIALISED});
	}

	my @mandatory_params = qw/WatermarkID /;
	for my $param (@mandatory_params) {
		unless ($params->{$param} = $passed_params->{$param}) {
			return $self->_construct_error("Missing mandatory parameter $param", $ERROR_CODE->{MISSING_REQUIRED_PARAMETER});
		}
	}
	
	return $self->_get_request($api_method, $params);
}

#END autogenerated subs

=head2 getSessionID

Returns the currently cached SessionID.

Parameters:

=over

No parameters

=back

=cut

sub getSessionID {
	my $self = shift;
	return $self->{sm_session};
}

=head2 images_upload

Uploads an image.

Parameters:

=over

=item ImageData (binary) (required)

=item FileName (string) (required)

=item AlbumID (integer)

=item ImageID (integer)

=item Caption (string)

=item Keywords (string)

=item Latitude (float?)

=item Longitude (float?)

=item Altitude (integer?)

=back

=cut

sub images_upload {
	my ( $self, $passed_params ) = @_;

	my $params;

	if ( defined $self->{sm_session} ) {
		$params->{SessionID} = $self->{sm_session};
	}
	else {
		return $self->_construct_error(
			'Please call one of the login* methods first.',
			$ERROR_CODE->{SESSION_NOT_INITIALISED}
		);
	}

	my @mandatory_params = qw/ImageData FileName/;
	for my $param (@mandatory_params) {
		unless ( $params->{$param} = $passed_params->{$param} ) {
			return $self->_construct_error(
				"Missing mandatory parameter $param",
				$ERROR_CODE->{MISSING_REQUIRED_PARAMETER}
			);
		}
	}

	my $request =
	  HTTP::Request->new( 'PUT',
		'http://upload.smugmug.com/' . $params->{FileName} );
	$request->content( $params->{ImageData} );
	$request->header(
		'X-Smug-SessionID'    => $params->{SessionID},
		'X-Smug-Version'      => $API_VERSION,
		'X-Smug-ResponseType' => 'JSON',
		'X-Smug-FileName'     => $params->{FileName},
	);

	my @optional_params =
	  qw/AlbumID ImageID Caption Keywords Latitude Longitude Altitude/;
	for my $param (@optional_params) {
		if ( defined $passed_params->{$param} ) {
			$request->header( "X-Smug-$param" => $passed_params->{$param} );
		}
	}

	my $response = $self->{ua}->request($request);
	if ( $response->is_success ) {
		return $self->{json}->decode( $response->content );
	}
	else {
		return $self->_construct_error( $response->status_line,
			$ERROR_CODE->{HTTP_ERROR} );
	}
}

sub _get_request {
	my ( $self, $method, $params ) = @_;

	my $response =
	  $self->{ua}->post( "$self->{sm_endpoint}?method=$method", $params );
	if ( $response->is_success ) {
		return $self->{json}->decode( $response->content );
	}
	else {
		return $self->_construct_error( $response->status_line,
			$ERROR_CODE->{HTTP_ERROR} );
	}
}

sub _construct_error {
	my ( $self, $message, $error_code ) = @_;
	return (
		{
			stat    => 'fail',
			message => $message,
			code    => $error_code,
		}
	);

}

=head1 DIAGNOSTICS

This section intentionally left blank.

=head1 CONFIGURATION AND ENVIRONMENT

WWW::SmugMUG::API requires no special configuration or environment variables.

=head1 DEPENDENCIES

JSON
LWP::UserAgent

=head1 INCOMPATIBILITIES

No known incompatibilities.

=head1 BUGS AND LIMITATIONS

No known bugs.

=head1 SUPPORT

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

    perldoc WWW::SmugMug::API

=head1 AUTHOR

Paul Arthur, C<< <flowerysong00 at yahoo.com> >>

=head1 ACKNOWLEDGEMENTS

This section intentionally left blank.

=head1 LICENSE AND COPYRIGHT

Copyright 2008-2010 Paul Arthur MacIain, all rights reserved.

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.


=cut

1;    # End of WWW::SmugMug::API