package Hashids::Util;

use strict;
use warnings;

our ( @EXPORT_OK, %EXPORT_TAGS );

BEGIN {
    use Exporter 'import';
    @EXPORT_OK = qw(consistent_shuffle to_alphabet from_alphabet bignum);
    %EXPORT_TAGS = ( all => \@EXPORT_OK );
}

use List::Util 'reduce';
use Math::BigInt;

use namespace::clean -except => [qw(import)];


sub consistent_shuffle {
    my ( $alphabet, $salt ) = @_;

    return wantarray ? [''] : '' unless $alphabet;

    my @alphabet
        = ref $alphabet eq 'ARRAY' ? @$alphabet : split // => $alphabet;
    return wantarray ? @alphabet : join '', @alphabet unless $salt;
    my @salt = ref $salt eq 'ARRAY' ? @$salt : split //, $salt;

    for ( my ( $i, $v, $p ) = ( $#alphabet, 0, 0 ); $i > 0; $i--, $v++ ) {
        $p += my $int = ord $salt[ $v %= @salt ];
        my $j = ( $int + $v + $p ) % $i;

        @alphabet[ $j, $i ] = @alphabet[ $i, $j ];
    }

    wantarray ? @alphabet : join '', @alphabet;
}

sub to_alphabet {
    my ( $num, $alphabet ) = @_;

    my $hash = '';
    my @alphabet
        = ref $alphabet eq 'ARRAY' ? @$alphabet : split // => $alphabet;

    $num = bignum($num);
    do {
        $hash
            = $alphabet[ bignum($num)->bmod( bignum( scalar @alphabet ) )
            ->numify ]
            . $hash;
        $num->bdiv( bignum( scalar @alphabet ) );
    } while ( $num->bcmp( bignum(0) ) );

    $hash;
}

sub from_alphabet {
    my ( $hash, $alphabet ) = @_;

    my @alphabet
        = ref $alphabet eq 'ARRAY' ? @$alphabet : split // => $alphabet;

    my $num = bignum(
        reduce { $a * @alphabet + $b }
        map { index join( '' => @alphabet ), $_ } split // => $hash
    );

    $num->bstr;
}

sub bignum {
    my $n = Math::BigInt->bzero();
    $n->round_mode('zero');
    return $n->badd("@{[shift]}");
}

1;
__END__

=encoding utf-8

=head1 NAME

Hashids::Util - Shuffling and conversion functions for Hashids

=head1 SYNOPSIS

    use Hashids::Util 
        qw( consistent_shuffle to_alphabet from_alphabet bignum );

=head1 DESCRIPTION

C<Hashids::Util> provides utility functions for L<Hashids> for
consistent shuffling of alphabets, conversion of numbers to hash
strings, and others.  These are originally included as private
functions, but are now provided here as a separate module as they can be
general enough to be used in other code.

C<Hashids::Util> does not export any functions by default.

=head1 FUNCTIONS

=head2 consistent_shuffle

    my $shuffled_alphabet = consistent_shuffle( $alphabet, $salt );

Given an alphabet and salt, produce a shuffled alphabet.  Both alphabet
and salt can be either strings or arrayrefs of characters.

=head2 to_alphabet

    my $hash = to_alphabet( $num, $alphabet );

Produce a hash string given a number and alphabet.  The given alphabet
may be a string or an arrayref of characters.

=head2 from_alphabet

    my $num = from_alphabet( $hash, $alphabet );

Produce a number from the given hash string and alphabet.  The given
alphabet may be a string or arrayref of characters.

=head2 bignum

   my $bignum = bignum( '1_152_921_504_606_846_976' );

Construct a L<Math::BigInt> scalar from the given number.

=head1 SEE ALSO

L<Hashids>

=head1 AUTHOR

Zak B. Elep E<lt>zakame@cpan.orgE<gt>

=cut
