package IO::Iron::Common;

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

use 5.010_000;
use strict;
use warnings;

# Global creator
BEGIN {
	# No exports
}

# Global destructor
END {
}


# ABSTRACT: Common routines for Client Libraries to Iron services IronCache, IronMQ and IronWorker.

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



use Path::Tiny qw{path};
use Try::Tiny;
use Log::Any qw{$log};
require JSON::MaybeXS;
use File::Spec ();
use File::HomeDir ();
use Hash::Util 0.06 qw{lock_keys unlock_keys};
use Carp::Assert::More;
use English '-no_match_vars';
use Params::Validate qw(:all);




sub IRON_CONFIG_KEYS {
	return (
        # Iron.io standard:
		'project_id',       # The ID of the project to use for requests.
		'token',            # The OAuth token that should be used to authenticate requests. Can be found in the HUD.
		'host',             # The domain name the API can be located at. Defaults to a product-specific value, but always using Amazon's cloud.
		'protocol',         # The protocol that will be used to communicate with the API. Defaults to "https", which should be sufficient for 99% of users.
		'port',             # The port to connect to the API through. Defaults to 443, which should be sufficient for 99% of users.
		'api_version',      # The version of the API to connect through. Defaults to the version supported by the client. End-users should probably never change this. Except: IronMQ service upgraded from v2 to v3 in 2015!
        # IO::Iron additions:
		'timeout',          # REST client timeout (for REST calls accessing IronMQ). N.B. This is not a IronMQ config option! It only configures client this client.
		'policies',         # Filename of JSON file containing policies.
	);
}


sub IRON_CLIENT_PARAMETERS {
	return (
			IRON_CONFIG_KEYS(),
			'config',            # The config file name.
			'connector',         # Reference to a preinitiated connector object.
#			'policy',            # Reference to a preinitiated policy hash.
	);
}


sub get_config { ## no critic (Subroutines::RequireArgUnpacking)
	my %params = validate(
		@_, {
			map { $_ => { type => SCALAR, optional => 1 }, } IRON_CONFIG_KEYS(), ## no critic (ValuesAndExpressions::ProhibitCommaSeparatedStatements)
			'config' => { type => SCALAR, optional => 1, },
		}
	);
	$log->tracef('Entering get_config(%s)', \%params);
	my %config = ( map { $_ => undef } IRON_CONFIG_KEYS() ); ## preset config keys.
	lock_keys(%config, IRON_CONFIG_KEYS());
	_read_iron_config_file(\%config, File::Spec->catfile(File::HomeDir->my_home, '.iron.json')); # Homedir
	_read_iron_config_env_vars(\%config); # Global envs
	_read_iron_config_file(\%config, File::Spec->catfile(File::Spec->curdir(), 'iron.json')); # current dir
	if(defined $params{'config'}) { # config file specified when creating the class, if given.
		_read_iron_config_file(\%config,
				File::Spec->file_name_is_absolute($params{'config'})
				? $params{'config'} : File::Spec->catfile(File::Spec->curdir(), $params{'config'})
				);
	}
	# The parameters given when the object was created, except 'config'
	my @copy_param_keys = grep { !/^config$/msx} keys %params;
	@config{@copy_param_keys} = @params{@copy_param_keys};

	$log->tracef('Exiting get_config: %s', \%config);
	return \%config;
}

# Replace the existing values in $config if new environment variables found.
#	Vars:
#	$config->{'project_id'}  = $ENV{'IRON_PROJECT_ID'}
#	$config->{'token'}       = $ENV{'IRON_TOKEN'}
#	$config->{'host'}        = $ENV{'IRON_HOST'}
#	$config->{'protocol'}    = $ENV{'IRON_PROTOCOL'}
#	$config->{'port'}        = $ENV{'IRON_PORT'}
#	$config->{'api_version'} = $ENV{'IRON_API_VERSION'}
#	$config->{'timeout'}     = $ENV{'IRON_TIMEOUT'}
sub _read_iron_config_env_vars {
	my ($config) = @_;
	$log->tracef('Entering _read_iron_config_env_vars(%s)', $config);
	foreach my $config_key (keys %{$config}) {
		if (defined $ENV{'IRON_' . uc $config_key}) {
			$config->{$config_key} = $ENV{'IRON_' . uc $config_key};
		}
	}
	$log->tracef('Exiting _read_iron_config_env_vars: %s', $config);
	return $config;
}


# Try to read the file given as second parameter. (if undef, fail).
# If fails, gracefully return 0; if succeed, change configuration (first parameter) and return 1.
sub _read_iron_config_file {
	my ($config, $full_path_name) = @_;
	$log->tracef('Entering _read_iron_config_file(%s, %s)', $full_path_name, $config);

	assert_nonblank( $full_path_name, 'full_path_name is not defined or is blank.' );

	my $read_config;
	my $rval;
   my $file = path($full_path_name);
	if ($file->is_file) {
		$log->tracef('File %s exists', $full_path_name);
      my $file_contents;
      try { $file_contents = $file->slurp_utf8 };
		if($file_contents) {
			$log->tracef('Slurped file %s', $full_path_name);
			my $json = JSON::MaybeXS->new(utf8 => 1, pretty => 1);
			$read_config = $json->decode($file_contents);
			foreach my $config_key (keys %{$config}) {
				if (defined $read_config->{$config_key}) {
					$config->{$config_key} = $read_config->{$config_key};
				}
			}
			$rval = 1;
		}
		else {
			$log->debugf('Could not read file %s', $full_path_name);
			$rval = 0;
		}
	}
	else {
		$log->tracef('File %s does not exist', $full_path_name);
		$rval = 0;
	}
	$log->tracef('Exiting _read_iron_config_file: %s', $config);
	return $rval;
}


#my $GEN_DELIMS = q{!} . q{$} . q{&} . q{'} . q{(} . q{)}
#                  . q{*} . q{+} . q{,} . q{;} . q{=};
#my $SUB_DELIMS = q{:} . q{/} . q{?} . q{#} . q{[} . q{]} . q{@};
#my $RESERVED_CHARACTERS = $GEN_DELIMS . $SUB_DELIMS;
#my $RFC_3986_RESERVED_CHARACTERS =~ s/(.{1})/\\$1/sg; # Escape every character.
sub contains_rfc_3986_res_chars {
	my @params = validate_pos( @_, { type => SCALAR } );
   ## no critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
	my $rfc_3986_reserved_characters = q{\!\$\&\'\(\)\*\+\,\;\=\:\/\?\#\[\]\@};
   ## critic (ValuesAndExpressions::RequireInterpolationOfMetachars)
	return ($params[0] =~ m/[$rfc_3986_reserved_characters]{1,}/msx) ? 1 : 0;
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

IO::Iron::Common - Common routines for Client Libraries to Iron services IronCache, IronMQ and IronWorker.

=head1 VERSION

version 0.13

=for stopwords IronCache IronMQ IronWorker config json Mikko Koivunalho

=head1 REQUIREMENTS

=head1 FUNCTIONS

Internal functions for use in the Client objects.

=head2 IRON_CONFIG_KEYS

=head2 IRON_CLIENT_PARAMETERS

=head2 get_config

Get the config from file or from system environmental variables.
Follows the global configuration scheme as explained in http://dev.iron.io/mq/reference/configuration/.

The configuration is constructed as follows:

=over 8

=item 1. The global configuration file sets the defaults according to the file hierarchy. (F<.iron.json> in home folder)

=item 2. The global environment variables overwrite the global configuration file's values.

=item 3. The product-specific environment variables overwrite everything before them.

=item 4. The local configuration file overwrites everything before it according to the file hierarchy. (F<iron.json> in the same directory as the script being run)

=item 5. The configuration file specified when instantiating the client library overwrites everything before it according to the file hierarchy.

=item 6. The arguments passed when instantiating the client library overwrite everything before them.

=back

Return: ref to %config.

=head2 contains_rfc_3986_res_chars

Check that the string does not contain any RFC 3986 Reserved Characters:

!$&'()*+,;=:/?#[]@

Return True (1) if contains. Otherwise False (0).

=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
