package Text::Roman;
# ABSTRACT: Allows conversion between Roman and Arabic algarisms.


use strict;
use warnings qw(all);

use Carp qw(carp);
use Exporter;

## no critic (ProhibitAutomaticExportation, ProhibitExplicitISA)
our @ISA            = qw(Exporter);
our %EXPORT_TAGS    = (all => [qw(roman int2roman roman2int isroman mroman2int milhar2int ismroman ismilhar)]);
our @EXPORT_OK      = @{$EXPORT_TAGS{all}};

our $VERSION = '3.4'; # VERSION

our @RSN = qw/I V X L C D M/;                           # Roman Simple Numerals
our @RCN = qw/IV IX XL XC CD CM/;                       # Roman Complex Numerals
our %R2A;
@R2A{@RSN, @RCN} = qw/
    1 5 10 50 100 500 1000 4 9 40 90 400 900
/;                                                      # numeric values
our %A2R = reverse %R2A;                                # reverse for convenience


sub isroman {
    local $_ = shift || $_;                             # roman algarism

    return if !/^[@RSN]+$/x;
    return if /([IXCM])\1{3,}|([VLD])\2+/ix;            # tests repeatability
    my @re = qw/IXI|XCX|CMC/;
    for (1 .. $#RSN) {
        push @re, "$RSN[$_ - 1]$RSN[$_]$RSN[$_ - 1]";   # tests IVI
        push @re, "$RSN[$_]$RSN[$_ - 1]$RSN[$_]";       # and VIV conditions
    }
    my $re = join "|", @re;
    return !/$re/x;
}


sub int2roman {
    my $n = shift || $_;                                # number, arabic numerals
    return if $n <= 0 or $n >= 4000;

    my $ret = "";
    for (reverse sort { $a <=> $b } values %R2A) {
        $ret .= $A2R{$_} x int($n / $_);
        $n %= $_;
    }
    return $ret;
}


sub roman2int {
    local $_ = uc(shift || $_);                         # roman algarism

    return unless isroman();

    my ($r, $ret, $_ret) = ($_, 0, 0);
    while ($r) {
        ## no critic (ProhibitMatchVars)
        $r =~ s/^$_//x && ($ret += $R2A{$&}, last) for @RCN, @RSN;
        return if $ret <= $_ret;
        $_ret = $ret;
    }
    return $ret;
}


sub ismilhar {
    local $_ = shift || $_;
    return unless /^[_@RSN]+$/x;

    my @r = split /_/;
    isroman() || return for @r;
    return 1;
}


sub milhar2int {
    local $_ = shift || $_;

    return unless ismilhar();

    my @r = split /_/;
    my $ret = roman2int(pop @r);
    $ret += 1000 * roman2int() for @r;
    return $ret;
}


sub ismroman {
    carp "deprecated function, use ismilhar instead";
    return ismilhar(shift);
}

sub mroman2int {
    carp "deprecated function, use milhar2int instead";
    return milhar2int(shift);
}

sub roman {
    carp "deprecated function, use int2roman instead";
    return int2roman(shift);
}

1;

__END__

=pod

=encoding utf8

=head1 NAME

Text::Roman - Allows conversion between Roman and Arabic algarisms.

=head1 VERSION

version 3.4

=head1 SYNOPSIS

    #!/usr/bin/env perl
    use strict;
    use warnings;
    use Text::Roman qw(:all);

    print int2roman(123), "\n";

    my $roman = "XXXV";
    print roman2int($roman), "\n" if isroman($roman);

    my $milhar = 'L_X_XXIII'; # = 60,023
    print milhar2int($milhar), "\n" if ismilhar($milhar);

=head1 DESCRIPTION

This package supports both conventional Roman algarisms (which range from I<1> to I<3999>) and Milhar Romans, a variation which uses a bar across the algarism to indicate multiplication by I<1_000>.
For the purposes of this module, acceptable syntax consists of an underscore suffixed to the algarism e.g. IV_V = I<4_005>.
The term Milhar apparently derives from the Portuguese word for "thousands" and the range of this notation extends the range of Roman numbers to I<3999 * 1000 + 3999 = 4_002_999>.

Note: the functions in this package treat Roman algarisms in a case-insensitive manner such that "VI" == "vI" == "Vi" == "vi".

The following functions may be imported into the caller package by name:

=head1 FUNCTIONS

=head2 isroman

Tests a string to be a valid Roman algarism.
Returns a boolean value.

=head2 int2roman

Converts an integer expressed in Arabic numerals, to its corresponding Roman algarism.
If the integer provided is out of the range expressible in Roman notation, an I<undef> is returned.

=head2 roman2int

Does the converse of L</int2roman>, converting a Roman algarism to its integer value.

=head2 ismilhar

Determines whether a string qualifies as a Milhar Roman algarism.

=head2 milhar2int

Converts a Milhar Roman algarism to an integer.

=head2 ismroman/mroman2int/roman

These functions belong to the module's old interface and are considered deprecated.
Do not use them in new code and they will eventually be discontinued; they map as follows:

=over 4

=item *

ismroman      => B<ismilhar>

=item *

mroman2int    => B<milhar2int>

=item *

roman         => B<int2roman>

=back

=head1 CHANGES

Some changes worth noting from this module's previous incarnation:

=over

=item I<namespace imports>

The call to B<use> must now explicitly request function names imported into it's namespace.

=item I<argument defaults/void context>

All functions now will operate on B<$_> when no arguments are passed, and will set B<$_> when called in a void context.
This allows for writing code like:

    @x = qw/V III XI IV/;
    roman2int() for @x;
    print join("-", @x);

instead of the uglier:

    @x = qw/V III XI IV/;
    $_ = roman2int($_) for @x;
    print join("-", @x);

=back

=head1 SPECIFICATION

Roman algarisms may be described using the following BNF-like formula:

    a   = I{1,3}
    b   = V\a?|IV|\a
    e   = X{1,3}\b?|X{0,3}IX|\b
    ee  = IX|\b
    f   = L\e?|XL\ee?|\e
    g   = C{1,3}\f?|C{0,3}XC\ee?|\f
    gg  = XC\ee?|\f
    h   = D\g?|CD\gg?|\g
    j   = M{1,3}\h?|M{0,3}CM\gg?|\h

=head1 REFERENCES

For a description of the Roman numeral system see: L<http://www.novaroma.org/via_romana/numbers.html>.
A reference to Milhar Roman alagarisms (in Portuguese) may be found at: L<http://web.archive.org/web/20020819094718/http://www.estado.com.br/redac/norn-nro.html>.

=head1 ACKNOWLEDGEMENTS

This module was originally written by Peter de Padua Krauss and submitted to CPAN by L<Stanislaw Pusep|https://metacpan.org/author/SYP> who has relinquished control to L<Erick Calder|https://metacpan.org/author/ECALDER> since the original author has never maintained it and can no longer be reached.

Erick have completely rewritten the module, implementing simpler algorithms to perform the same functionality, adding a test suite, a Changes file, etc. and providing more comprehensive documentation.

Ten years later, Stanislaw returned as a maintainer.

=head1 AUTHOR

Stanislaw Pusep <stas@sysd.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2003 by Erick Calder <ecalder@cpan.org>.

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

=cut
