#!perl

use warnings;
use strict;
use Math::Recaman qw(recaman recaman_a008336);
use Getopt::Long;
use Pod::Usage;

our $SVG;
our $MIDI;
our %SVG_COLORS = map { split ' ' } <DATA>; # read standard SVG colors from the DATA section
BEGIN {
    eval { require SVG };
    unless ($@ ) {
        SVG->import();
        $SVG = 1;
    } else {
        $SVG = 0;
    }

    eval {
        require MIDI::Simple;
        require IO::String;
    };
    unless ($@ ) {
        MIDI::Simple->import();
        IO::String->import();
        $MIDI = 1;
    } else {
        $MIDI = 0;
    }
}

my $help = 0;
my $man  = 0;
my $alt  = 0;
my $mode;
my $output;
my $flags;

GetOptions(
  'help|?'     => \$help,
  'man'        => \$man,
  'mode|m=s'   => \$mode,
  'output|o=s' => \$output,
  'flags|f=s'  => \$flags,
  'alt|a'      => \$alt,
) or pod2usage(2);

pod2usage(1) if $help;
pod2usage(-exitval => 0, -verbose => 2) if $man;

my %flags = map { split '=' } split(' ', $flags); # Split up key=value pairs
my $target = shift @ARGV || 71;

my $func = ($alt) ? \&recaman_a008336 : \&recaman;
if (!defined $mode || lc($mode) eq 'default') {
  do_default($target);
} elsif (lc($mode) eq 'check') {
  do_check();
} elsif (lc($mode) eq 'newline' || lc($mode) eq 'nl') {
  do_newline($target);
} elsif (lc($mode) eq 'nth') {
  do_nth($target);
} elsif (lc($mode) eq 'max') {
  do_max($target);
} elsif (lc($mode) eq 'svg') {
  do_svg($target, $output, %flags);
} elsif (lc($mode) eq 'midi') {
  do_midi($target, $output, %flags);
} else {
  die "Unknown mode '$mode'\n";
}

sub do_default {
  my $target = shift;
  print join(", ", $func->($target))."\n";
}



sub do_check {
  do_default($alt ? 34 : 71);
  my @checks = $alt ? @Math::Recaman::a008336_checks : @Math::Recaman::checks;
  print join(", ", @checks)."\n";
}

sub do_newline {
  my $target = shift;
  $func->($target);
}

sub do_nth {
  my $target = shift;
  my ($last, $max) = _nth($target);
  print "$last\n";
}

sub do_max{
  my $target = shift;
  my ($last, $max) = _nth($target);
  print "$max\n";
}

sub _nth {
  my $target = shift;
  my $last = 0;
  my $max  = 0;
  $func->($target, sub { $last = shift; $max = $last if $last>$max });
  return ($last, $max);
}

sub do_svg {
  my $target = shift;
  my $output = shift;
  my %flags  = @_;
  die "You must install the Perl module SVG from CPAN to use this feature\n" unless $SVG;

  my $STRIDE     = $flags{stride} || 15; # pixels between numbers
  my $prev       = 0;
  my $max_num    = 0;
  my $max_radius = 0;
  my @arcs;
  $func->($target, sub {
    my $num     = shift;
    my $count   = shift;

    my $low     = ($prev, $num)[$prev > $num];
    my $high    = ($prev, $num)[$prev < $num];
    my $start   = ($low+1)*$STRIDE;
    my $end     = ($high+1)*$STRIDE;
    my $dist    = $end-$start;
    my $radius  = ($dist/2);

    push @arcs, { num => $num, max_cur => $max_num, start => $start, end => $end, radius => $radius };

    $max_num    = $num if $num>$max_num;
    $max_radius = $radius if $radius>$max_radius;
    $prev       = $num;
  });

  # Create Canvas
  my $width  = ($max_num+2)*$STRIDE;
  my $height = ($max_radius+$STRIDE)*2;
  my $svg = SVG->new(width => $width, height => $height);

  # Draw main line
  $svg->line(
      id => 'mainline',
      x1 => $STRIDE,
      y1 => $height/2,
      x2 => $width-$STRIDE,
      y2 => $height/2,
      stroke => 'black',
      fill   => 'none',
      'stroke-width' => 2,
  );

  # Draw '$target' number of circles
  $svg->circle(
      id => "circle_$_",
      cx => ($_+1)*$STRIDE,
      cy => $height/2,
      r  => 2
  ) for (0..$max_num);

  # Draw the arcs
  my $count = 0;
  foreach my $arc (@arcs) {
    my $prev    = $arc->{max_cur};
    my $num     = $arc->{num};
    my $start   = $arc->{start};
    my $end     = $arc->{end};
    my $radius  = $arc->{radius};
    my $y       = $height/2;
    my $over    = (($flags{'side-choice'} || "") eq 'backwards') ? $num>=$prev : $num%2; #
    my $top     = ($over) ? $y-$radius : $y+$radius;
    my $mid     = $start+$radius;
    # Draw an arc
    my $path = (($flags{'arc-style'} || "") eq 'streamlined') ?
      "M $start,$y C $start $y, $mid $top, $end $y" :       # old style
      "M $start,$y A $radius, $radius 180 1 $over $end,$y"; # semi circle
    my $first_color  = $flags{'first-color'}  || 'black';
    my $second_color = $flags{'second-color'} || 'blue';

    $first_color  = $SVG_COLORS{$first_color}  || $first_color;
    $second_color = $SVG_COLORS{$second_color} || $second_color;
    my $color     = ($prev>$num) ?  $first_color : $second_color;
    $color        = _gradient_color($first_color, $second_color, $radius/$y) if (($flags{'color-mode'} || "") eq 'gradient');

    $svg->path(
        id      => "arc_$count",
        d       => $path,
        stroke  => $color,
        fill    => 'none',
        'stroke-width' => ($flags{'stroke-width'} || 3),
    );
    $count++;
  }
  # Print out the SVG
  _dump($svg->xmlify, $output);
}

sub do_midi {
  my $target = shift;
  my $output = shift;
  my %flags  = @_;
  die "You must install the Perl modules MIDI::Simple and IO::String from CPAN to use this feature\n" unless $MIDI;

  # Create the midi file
  my $midi = MIDI::Simple->new_score();
  # Set up
  # The default tempo is 96
  # Setting the tempo to 120 was interesting and matches what the oeis-to-midi script does
  # https://github.com/synchronometry/OEIS-to-MIDI
  # https://oeis.org/play?seq=A005132
  $midi->set_tempo($flags{tempo}) if $flags{tempo};
  $midi->text_event("Representation of Recamán's sequence for the first $target notes");
  #$midi->noop("c1 f, o5");
  # Set up the notes
  $func->($target, sub {
    my $num   = shift;
    my $count = shift;
    # Add note as $num % 127
    $midi->n("n".($num%127));
  });

  # TODO don't love the reliance on IO::String
  my $io = IO::String->new;
  $midi->write_score($io);

  # Print out the Midi file
  _dump(${$io->string_ref}, $output);
}

sub _dump {
  my $data   = shift;
  my $output = shift;
  if ($output) {
    open(my $fh, '>', $output) || die "Couldn't open output file $output: $!\n";
    binmode($fh);
    print $fh $data;
    close($fh);
  } else {
    binmode(STDOUT);
    print $data;
  }
}

sub _gradient_color {
  my $start = shift;
  my $end   = shift;
  my $frac  = shift;

  my ($sr,$sg,$sb) = map { hex } ($start =~ m!#?([a-z\d]{2})([a-z\d]{2})([a-z\d]{2})!i);
  my ($er,$eg,$eb) = map { hex } ($end   =~ m!#?([a-z\d]{2})([a-z\d]{2})([a-z\d]{2})!i);

  my $r = int($sr+(($er-$sr)*$frac)+0.5);
  my $g = int($sg+(($eg-$sg)*$frac)+0.5);
  my $b = int($sb+(($eb-$sb)*$frac)+0.5);

  return sprintf("#%.2x%.2x%.2x", $r, $g, $b);
}

=encoding utf8

=head1 NAME

recaman - generate Recamán's sequence in various formats

=head1 USAGE

  recaman [n]

Will generate the first C<n> numbers of the sequence. The default is the first 71 numbers.

  recaman --help
  recaman --man

Will print out help messages.

You can also change the C<mode> to output in different styles.

  recaman --mode check
  recaman --mode nl 100
  recaman --mode nth 1000
  recaman --mode max 10000000

And use C<flags> to set various options

  recaman --mode svg --flags "first-color=#0000ff second-color=gold stroke-width=10 stride=20 color-mode=gradient" 75

=head1 OPTIONS

=over 4

=item --help
=item --man

Print out help and usage.

=item --mode | -m

Choose the output mode (see L</MODES> below).

=item --output | -o

Output file for C<MIDI> and C<SVG> modes.

=item --flags | -f

Optional flags for different modes (see L</FLAGS> below).

=item --alt | -a

Whether to use the standard Recamán's sequence or the alternative A008336 version.

=back

=head1 MODES

=over 4

=item default

Prints out the requested numbers in the sequence on one line separated by commas.

=item check

Prints out the first 71 numbers in the sequence plus, immediately, underneath, the same 71 numbers from a different source.

=item newline or nl

Prints out the requested numbers separate by newlines. This prints each number out as it calculates it so it uses less memory for large sequences.

=item nth

Prints out only the nth number.

=item max

Prints out the largest number seen when calculating the nth number.

=back

=head1 FLAGS

=head2 SVG mode

=over 4

=item stride

Distance between numbers n the number line.

Default is C<15>.

=item first-color

The first color to use.

Default is C<black>.

=item second-color

The second color to use. Can be any HTML style hex color (e.g C<#ffd700>) or one of the standard L<SVG named colors|https://developer.mozilla.org/en-US/docs/Web/CSS/named-color>.

Default is C<blue>.

=item color-mode

If C<color-mode> is set to C<gradient> then the colors of the arcs will gradually fade from C<first-color> to C<second-color>.

Default is C<choice> which switch between C<first-color> to C<second-color>.

=item stroke-width

The width of the lines of arcs.

Default is C<3>.

=item side-choice

Which side to draw arcs on.

Default is C<mod> which alternates between top and bottom.

If you choose C<backwards> then it will draw arcs going backwards under the number line and those going forwards on top.

=item arc-style

How to draw the arcs.

Default is C<semicircle> which draws, err, a semi-circle.

You can also choose C<streamlined> which draws a sleeker, lower profile arc. This was the default in C<v1.0>.

=back

=head2 MIDI mode

=over 4

=item tempo

Default is C<96>.

C<120> is also interesting and matches what OEIS-to-Midi does.

See L<https://github.com/synchronometry/OEIS-to-MIDI> and L<https://oeis.org/play?seq=A005132>.

=back

=head1 AUTHOR

Simon Wistow <simon@thegestalt.org>

=head1 COPYRIGHT

Copyright (C) Simon Wistow, 2024

=cut

__DATA__
aliceblue #F0F8FF
antiquewhite #FAEBD7
aqua #00FFFF
aquamarine #7FFFD4
azure #F0FFFF
beige #F5F5DC
bisque #FFE4C4
black #000000
blanchedalmond #FFEBCD
blue #0000FF
blueviolet #8A2BE2
brown #A52A2A
burlywood #DEB887
cadetblue #5F9EA0
chartreuse #7FFF00
chocolate #D2691E
coral #FF7F50
cornflowerblue #6495ED
cornsilk #FFF8DC
crimson #DC143C
cyan #00FFFF
darkblue #00008B
darkcyan #008B8B
darkgoldenrod #B8860B
darkgray #A9A9A9
darkgreen #006400
darkgrey #A9A9A9
darkkhaki #BDB76B
darkmagenta #8B008B
darkolivegreen #556B2F
darkorange #FF8C00
darkorchid #9932CC
darkred #8B0000
darksalmon #E9967A
darkseagreen #8FBC8F
darkslateblue #483D8B
darkslategray #2F4F4F
darkslategrey #2F4F4F
darkturquoise #00CED1
darkviolet #9400D3
deeppink #FF1493
deepskyblue #00BFFF
dimgray #696969
dimgrey #696969
dodgerblue #1E90FF
firebrick #B22222
floralwhite #FFFAF0
forestgreen #228B22
fuchsia #FF00FF
gainsboro #DCDCDC
ghostwhite #F8F8FF
gold #FFD700
goldenrod #DAA520
gray #808080
green #008000
greenyellow #ADFF2F
grey #808080
honeydew #F0FFF0
hotpink #FF69B4
indianred #CD5C5C
indigo #4B0082
ivory #FFFFF0
khaki #F0E68C
lavender #E6E6FA
lavenderblush #FFF0F5
lawngreen #7CFC00
lemonchiffon #FFFACD
lightblue #ADD8E6
lightcoral #F08080
lightcyan #E0FFFF
lightgoldenrodyellow #FAFAD2
lightgray #D3D3D3
lightgreen #90EE90
lightgrey #D3D3D3
lightpink #FFB6C1
lightsalmon #FFA07A
lightseagreen #20B2AA
lightskyblue #87CEFA
lightslategray #778899
lightslategrey #778899
lightsteelblue #B0C4DE
lightyellow #FFFFE0
lime #00FF00
limegreen #32CD32
linen #FAF0E6
magenta #FF00FF
maroon #800000
mediumaquamarine #66CDAA
mediumblue #0000CD
mediumorchid #BA55D3
mediumpurple #9370DB
mediumseagreen #3CB371
mediumslateblue #7B68EE
mediumspringgreen #00FA9A
mediumturquoise #48D1CC
mediumvioletred #C71585
midnightblue #191970
mintcream #F5FFFA
mistyrose #FFE4E1
moccasin #FFE4B5
navajowhite #FFDEAD
navy #000080
oldlace #FDF5E6
olive #808000
olivedrab #6B8E23
orange #FFA500
orangered #FF4500
orchid #DA70D6
palegoldenrod #EEE8AA
palegreen #98FB98
paleturquoise #AFEEEE
palevioletred #DB7093
papayawhip #FFEFD5
peachpuff #FFDAB9
peru #CD853F
pink #FFC0CB
plum #DDA0DD
powderblue #B0E0E6
purple #800080
red #FF0000
rosybrown #BC8F8F
royalblue #4169E1
saddlebrown #8B4513
salmon #FA8072
sandybrown #F4A460
seagreen #2E8B57
seashell #FFF5EE
sienna #A0522D
silver #C0C0C0
skyblue #87CEEB
slateblue #6A5ACD
slategray #708090
slategrey #708090
snow #FFFAFA
springgreen #00FF7F
steelblue #4682B4
tan #D2B48C
teal #008080
thistle #D8BFD8
tomato #FF6347
turquoise #40E0D0
violet #EE82EE
wheat #F5DEB3
white #FFFFFF
whitesmoke #F5F5F5
yellow #FFFF00
yellowgreen #9ACD32
