#!/usr/bin/perl
use warnings;
use strict;

use POSIX qw(ceil);
use constant { FALSE => 0 };

print STDERR<<EOF
shelldice.pl 1.1, by jl @ 11.06.2011. roll dices for RPGs or other purposes
usage: dice.exe "2d6<+1"\trolls 2 x d6 and ignores smallest roll and adds one. on windows
       ./dice.pl '4d8>2'\trolls 4 x d6 and ignores two largest rolls. on *nix
       ./dice 3d8-2 d100\trolls first 3 x d8 and substracts 2 and as a totally separate equation rolls 1 x d100
EOF

unless(@ARGV);

for my $roll (@ARGV)
{
	my $result = 0;

	# somewhat validate input
	if($roll !~ /^\d*d\d+ ([<>]\d*)? ([\+-] ( (\d+) | ((\d*d\d+) ([<>]\d*)?) ) )*$/xi)
	{
		print STDERR "Invalid character(s) in equation or incorrect syntax\n";
		next;
	}

	# put throws to @throws and remove them from the string
	for(my $i = 0; $roll =~ /(\d*)?d(\d+)((?:<|>)\d*)?/i; $i++)
	{
		my $times       = $1 ? $1 : 1;
		my $dice_sides  = $2;
		my $drop_throws = $3 ? $3 : FALSE;
		my @throws;

		((print STDERR "Sides of a dice is missing or it's zero") && next) 
			unless(defined $dice_sides && $dice_sides);

		for(my $j = 0; $j < $times; $j++)
			{ $throws[$j] = throw($dice_sides); }

		# remove min(s) or max(s)
		if($drop_throws)
		{
			@throws = $drop_throws =~ /</ ? sort { $a <=> $b } @throws : sort { $b <=> $a } @throws;
			my $num_slice = $drop_throws =~ /(\d+)/ ? $1 : 1;
			splice(@throws, 0, $num_slice);
		}

		my $throw_addition = '(' . join('+', @throws) . ')'; 
		$roll =~ s/(\d*)?d(\d+)((?:<|>)\d*)?/$throw_addition/;
	}

	# handle possible multiple sequntial + or - signs
	while($roll =~ /([\+-]{2,})/g)
	{
		my $match = $1;
		my $minuses = 0;
		for(split('', $match))
			{ $minuses++ if($_ eq '-'); }
		my $sign = $minuses % 2 ? '-' : '+';

		$match = join('', map { '\\'.$_ } split('', $match));
		$roll =~ s/$match/$sign/;
	}

	# separate adjacent characters with spaces
	print $roll, " = ";
	print eval $roll, "\n"; 
}

exit(0);

# roll a dice. TODO should seed for rand() be initialized explicitly? randomness seems to be quite good
sub throw
{
	my $dice_sides = shift;

	return ceil(rand($dice_sides));
}

