package IO::Iron::IronCache::Cache;

## no critic (Documentation::RequirePodAtEnd)
## no critic (Documentation::RequirePodSections)
## no critic (ControlStructures::ProhibitPostfixControls)
## no critic (Subroutines::RequireArgUnpacking)

use 5.010_000;
use strict;
use warnings;

# Global creator
BEGIN {
	# Export Nothing
    use parent qw(IO::Iron::IronCache::Policy); # Inheritance
}

# Global destructor
END {
}


# ABSTRACT: IronCache (Online Item-Value Storage) Client (Cache).

our $VERSION = '0.13'; # VERSION: generated by DZP::OurPkgVersion



use Log::Any  qw($log);
use Hash::Util 0.06 qw{lock_keys unlock_keys};
use Carp::Assert::More;
use English '-no_match_vars';
use Params::Validate qw(:all);

use IO::Iron::Common;
use IO::Iron::IronCache::Api;

# CONSTANTS for this module

# DEFAULTS



sub new {
	my ($class, $params) = @_;
	$log->tracef('Entering new(%s, %s)', $class, $params);
	my $self;
	my @self_keys = ( ## no critic (CodeLayout::ProhibitQuotedWordLists)
			'ironcache_client',      # Reference to IronCache client
			'name',                  # Cache name
			'connection',            # Reference to REST client
			'policy',                # The policies. (copied from the client.)
			'last_http_status_code', # After successfull network operation, the return value is here.
	);
	lock_keys(%{$self}, @self_keys);
	$self->{'ironcache_client'} = defined $params->{'ironcache_client'} ? $params->{'ironcache_client'} : undef;
	$self->{'name'} = defined $params->{'name'} ? $params->{'name'} : undef;
	$self->{'connection'} = defined $params->{'connection'} ? $params->{'connection'} : undef;
    $self->{'policy'} = defined $params->{'policy'} ? $params->{'policy'} : undef;
	assert_isa( $self->{'ironcache_client'}, 'IO::Iron::IronCache::Client' , 'self->{ironcache_client} is IO::Iron::IronCache::Client.');
	assert_nonblank( $self->{'name'}, 'self->{name} is defined and is not blank.' );
	assert_isa( $self->{'connection'}, 'IO::Iron::Connection' , 'self->{connection} is IO::Iron::Connection.');
    assert_hashref( $self->{'policy'}, 'self->{policy} is a reference to a hash.');

	unlock_keys(%{$self});
	my $blessed_ref = bless $self, $class;
	lock_keys(%{$self}, @self_keys);

	$log->tracef('Exiting new: %s', $blessed_ref);
	return $blessed_ref;
}


sub name { return $_[0]->_access_internal('name', $_[1]); }

# TODO Move _access_internal() to IO::Iron::Common.

sub _access_internal {
	my ($self, $var_name, $var_value) = @_;
	$log->tracef('_access_internal(%s, %s)', $var_name, $var_value);
	if( defined $var_value ) {
		$self->{$var_name} = $var_value;
		return $self;
	}
	else {
		return $self->{$var_name};
	}
}


sub clear {
	my $self = shift;
	my %params = validate(
		@_, {
			# No parameters
		}
	);
	$log->tracef('Entering clear()');

	my $cache_name = $self->name();
	my $connection = $self->{'connection'};
	my ($http_status_code, $response_message) = $connection->perform_iron_action(
			IO::Iron::IronCache::Api::IRONCACHE_CLEAR_A_CACHE(),
			{
				'{Cache Name}' => $cache_name,
			}
		);
	$self->{'last_http_status_code'} = $http_status_code;

	$log->tracef('Exiting clear: %d', 1);
	return 1;
}


sub put {
	my $self = shift;
	my %params = validate(
		@_, {
			'key' => { type => SCALAR, callbacks => {
                    'RFC 3986 reserved character check' => sub { return ! IO::Iron::Common::contains_rfc_3986_res_chars(shift) },
                }}, # cache item key.
			'item' => { isa => 'IO::Iron::IronCache::Item', }, # cache item.
		}
	);
	$log->tracef('Entering put(%s)', \%params);

    $self->validate_item_key('key' => $params{'key'});
	my $cache_name = $self->name();
	my $connection = $self->{'connection'};
	my %item_body;
	foreach my $field_name (keys %{ IO::Iron::IronCache::Api::IRONCACHE_PUT_AN_ITEM_INTO_A_CACHE()->{'request_fields'}}) {
		if (defined $params{'item'}->{$field_name}) {
			$item_body{$field_name} = $params{'item'}->{$field_name};
		};
	}
	my ($http_status_code, $response_message) = $connection->perform_iron_action(
			IO::Iron::IronCache::Api::IRONCACHE_PUT_AN_ITEM_INTO_A_CACHE(),
			{
				'{Cache Name}' => $cache_name,
				'{Key}'        => $params{'key'},
				'body'         => \%item_body,
			}
		);
	$self->{'last_http_status_code'} = $http_status_code;

	$log->tracef('Exiting put: %d', 1);
	return 1;
}


# TODO Correct documentation: if item does not exist, it is created.

sub increment {
	my $self = shift;
	my %params = validate(
		@_, {
			'key' => { type => SCALAR, callbacks => {
                    'RFC 3986 reserved character check' => sub { return ! IO::Iron::Common::contains_rfc_3986_res_chars(shift) },
                }}, # cache item key.
			'increment' => { type => SCALAR, }, # cache item increment.
		}
	);
	assert_nonblank( $params{'key'}, 'key is defined and is not blank.' );
	assert_integer( $params{'increment'}, 'increment amount is integer.');
	$log->tracef('Entering increment(%s)', \%params);

    $self->validate_item_key('key' => $params{'key'});
	my $cache_name = $self->name();
	my $connection = $self->{'connection'};
	my %item_body;
	$item_body{'amount'} = $params{'increment'};
	my ($http_status_code, $response_message) = $connection->perform_iron_action(
			IO::Iron::IronCache::Api::IRONCACHE_INCREMENT_AN_ITEMS_VALUE(),
			{
				'{Cache Name}' => $cache_name,
				'{Key}'        => $params{'key'},
				'body'         => \%item_body,
			}
		);
	$self->{'last_http_status_code'} = $http_status_code;
	my $new_value = $response_message->{'value'};

	$log->tracef('Exiting increment: %d', $new_value);
	return $new_value;
}


sub get {
	my $self = shift;
	my %params = validate(
		@_, {
			'key' => { type => SCALAR, }, # cache item key.
		}
	);
	assert_nonblank( $params{'key'}, 'key is defined and is not blank.' );
	$log->tracef('Entering get(%s)', \%params);

	my $cache_name = $self->name();
	my $connection = $self->{'connection'};
	my ($http_status_code, $response_message) = $connection->perform_iron_action(
			IO::Iron::IronCache::Api::IRONCACHE_GET_AN_ITEM_FROM_A_CACHE(),
			{
				'{Cache Name}' => $cache_name,
				'{Key}'        => $params{'key'},
			}
		);
	$self->{'last_http_status_code'} = $http_status_code;
	my $new_item = IO::Iron::IronCache::Item->new(
		'value' => $response_message->{'value'},
		'cas' => $response_message->{'cas'},
	);
	$new_item->expires($response_message->{'expires'}) if defined $response_message->{'expires'};
	$new_item->replace($response_message->{'replace'}) if defined $response_message->{'replace'};
	$new_item->add($response_message->{'add'}) if defined $response_message->{'add'};

	$log->tracef('Exiting get: %s', $new_item);
	return $new_item;
}


sub delete { ## no critic (Subroutines::ProhibitBuiltinHomonyms)
	my $self = shift;
	my %params = validate(
		@_, {
			'key' => { type => SCALAR, }, # cache item key.
		}
	);
	assert_nonblank( $params{'key'}, 'key is defined and is not blank.' );
	$log->tracef('Entering delete(%s)', \%params);

	my $cache_name = $self->name();
	my $connection = $self->{'connection'};
	my ($http_status_code, $response_message) = $connection->perform_iron_action(
			IO::Iron::IronCache::Api::IRONCACHE_DELETE_AN_ITEM_FROM_A_CACHE(),
			{
				'{Cache Name}' => $cache_name,
				'{Key}'        => $params{'key'},
			}
		);
	$self->{'last_http_status_code'} = $http_status_code;

	$log->tracef('Exiting delete: %d', 1);
	return 1;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

IO::Iron::IronCache::Cache - IronCache (Online Item-Value Storage) Client (Cache).

=head1 VERSION

version 0.13

=head1 SYNOPSIS

Please see IO::Iron::IronCache::Client for usage.

=for stopwords IronCache Params IronHTTPCallException Mikko Koivunalho

=head1 REQUIREMENTS

=head1 SUBROUTINES/METHODS

=head2 new

=over

=item Creator function.

=back

=head2 Getters/setters

Set or get a property.
When setting, returns the reference to the object.

=over 8

=item name         Cache name.

=back

=head2 clear

Deletes all items in an IronCache cache.

=over 8

=item Params: [NONE]

=item Return: 1 == success.

=item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)

=back

=head2 put

=over

=item Params: key, IO::Iron::IronCache::Item object.

=item Return: 1 == success.

=item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)

=back

=head2 increment

=over

=item Params: key, increment (integer number, can be negative).

=item Return: the new value.

=item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)

=back

=head2 get

=over

=item Params: key.

=item Return: IO::Iron::IronCache::Item object.

=item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)

=back

=head2 delete

=over

=item Params: key.

=item Return: 1 if successful.

=item Exception: IronHTTPCallException if fails. (IronHTTPCallException: status_code=<HTTP status code> response_message=<response_message>)

=back

=head1 AUTHOR

Mikko Koivunalho <mikko.koivunalho@iki.fi>

=head1 BUGS

Please report any bugs or feature requests to bug-io-iron@rt.cpan.org or through the web interface at:
 http://rt.cpan.org/Public/Dist/Display.html?Name=IO-Iron

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2017 by Mikko Koivunalho.

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

The full text of the license can be found in the
F<LICENSE> file included with this distribution.

=cut
