#!/usr/bin/perl

use strict;
use warnings;
use Getopt::Long qw(:config no_ignore_case);
use List::Util qw( shuffle );
use List::MoreUtils qw( none );
use Readonly;

sub print_usage {
	print <<'_END_HELP';
$0 [-l | --length LENGTH] [-n | --no-special] [-r | --repeat NUMBER] [-e | --readable] [--verify | --no-verify]
_END_HELP
	exit 0;
}

our $VERSION = '0.05';

srand;

Readonly my $EMPTY     => q{};

my @small_caps_chars   = ( 'a'..'z' );
my @large_caps_chars   = ( 'A'..'Z' );
my @numerical_chars    = ( '0'..'9' );
my @unreadable_chars   = split $EMPTY, q{oO0l1I};
my @special_chars      = split $EMPTY, q{!@#$%^&*()};
my @chars;

my $num_of_types       = 0;
my $special            = 1;
my $length             = 15;
my $repeat             = 10;
my $readable           = 0;
my $verify             = 1;

my %verified = (
	'small_caps', $EMPTY,
	'large_caps', $EMPTY,
	'numerical',  $EMPTY,
	'special',    $EMPTY,
	'unreadable', $EMPTY,
);

GetOptions(
        'l|length=i'   => \$length,
        'r|repeat=i'   => \$repeat,
        'n|no-special' => sub { $special = 0; },
        'e|readable'   => \$readable,
        'verify!'      => \$verify,
        'h|help'       => sub { print_usage(); },
) or exit 2;

push @chars, @small_caps_chars, @large_caps_chars, @numerical_chars;
$num_of_types += 3;

if ($special) {
	push @chars, @special_chars;
	$num_of_types++;
}

if ($readable) {
	@chars = grep {
		    local $a = $_;
		    none { $a eq $_ } @unreadable_chars;
		  } @chars;
} else {
	$num_of_types++;
}

if ($num_of_types > $length) {
	die "You wanted a longer string that the variety of characters you've selected.\n"
	  . "You requested $num_of_types types of characters but only have $length length.\n";
}

for (1 .. $repeat) {
	my $password = $EMPTY;
	while ($length > length $password) {
		my $char = $chars[int rand @chars];

		if ($verify) {
			# very comfortable for debugging
			#print "-> CHAR: $char\n";
			#print "(order: small, large, numerical, special, unreadable)\n";
			#use Data::Dumper; print Dumper(\%verified);

			# verify small caption characters
			if ( !$verified{small_caps} ) {
				if ( none { $char eq $_ } @small_caps_chars ) {
					next;
				} else {
					$password .= $char;
					$verified{small_caps}++;
					next;
				}
			}

			# verify large caption characters
			if ( !$verified{large_caps} ) {
				if ( none { $char eq $_ } @large_caps_chars ) {
					next;
				} else {
					$password .= $char;
					$verified{large_caps}++;
					next;
				}
			}

			# verify numerical characters
			if ( !$verified{numerical} ) {
				if ( none { $char eq $_ } @numerical_chars ) {
					next;
				} else {
					$password .= $char;
					$verified{numerical}++;
					next;
				}
			}

			# verify special characters
			if ( !$verified{special} ) {
				if ( ($special) && ( none { $char eq $_ } @special_chars ) ) {
					next;
				} else {
					$password .= $char;
					$verified{special}++;
					next;
				}
			}

			# verify unreadable characters
			if ( !$verified{unreadable} ) {
				if ( (!$readable) && ( none { $char eq $_ } @unreadable_chars ) ) {
					next;
				} else {
					$password .= $char;
					$verified{unreadable}++;
					next;
				}
			}

			$password .= $char;
		} else {
			$password .= $char;
		}
	}

	print shuffle( split //, $password ) , "\n";

	# clear out the verifications
	foreach my $key ( keys %verified ) {
		$verified{$key} = $EMPTY;
	}
}


__END__

#################### main pod documentation begin ###################
=head1 NAME

genpass - Quickly create secure passwords

=head1 SYNOPSIS

genpass [-l | --length LENGTH] [-n | --no-special] [-r | --repeat NUMBER] [--verify | --no-verify]

    -l | --length          password length
    -r | --repeat NUMBER   NUMBER of passwords to output
    -n | --no-special      do NOT include special characters: '!','@','#','$','%','^','&','*','(',')' 
    -e | --readable        print only easily readable characters (no "o", "O", "0", "l", "1", "I")
       | --verify          makes sure it's got every type of char (slower), default behavior
       | --no-verify       doesn't make sure you got every type of char
    -h | --help            print a small usage line

=head1 DESCRIPTION

There are many jobs in which you just need to create a fast and secure password.
Sometimes you need one without special characters and sometimes you need to have a minimum length as well.
This script makes it possible, quickly and easily.

=head1 BUGS

None that I know of. Please report if and when you find any.

=head1 SUPPORT

If you have any problems or questions, contact me using the details below.

=head1 AUTHOR

    Sawyer X
    CPAN ID: XSAWYERX
    xsawyerx@cpan.org

=head1 COPYRIGHT

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

The full text of the license can be found in the
LICENSE file included with this module.

=head1 A Word on Moral

Our lives depend on the decisions of others in the world, and so, other lives depend on our decisions.
When we decide to consume animals, we dedicate the death of others, and it is something to consider giving up.
Please review http://www.meatstinks.com and http://www.milksucks.com .
Thank you.

=head1 SEE ALSO

perl(1).

=cut

#################### main pod documentation end ###################

