#!/usr/bin/perl -w

# Copyright 2012, 2013 Kevin Ryde

# This file is part of Math-NumSeq.
#
# Math-NumSeq is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3, or (at your option) any later
# version.
#
# Math-NumSeq is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
# or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
# for more details.
#
# You should have received a copy of the GNU General Public License along
# with Math-NumSeq.  If not, see <http://www.gnu.org/licenses/>.


# cf A095734 least 0<->1 flips to make Zeck into palindrome
#            A037888 least 0<->1 to make binary palindrome
#

use 5.004;
use strict;
use Test;
plan tests => 29;

use lib 't','xt';
use MyTestHelpers;
MyTestHelpers::nowarnings();
use MyOEIS;

use Math::Prime::XS 0.23 'is_prime'; # version 0.23 fix for 1928099
use Math::NumSeq::Fibbinary;

# uncomment this to run the ### lines
# use Smart::Comments '###';


#------------------------------------------------------------------------------
# A095309 -- palindrome in both binary and Zeckendorf base
# cf A006995 binary palindromes
#    A094202 Zeckendorf palindromes

MyOEIS::compare_values
  (anum => 'A095309',
   # max_value => 100000,
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Palindromes;
     my $palindrome = Math::NumSeq::Palindromes->new (radix => 2);
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got = (1,3);
     $palindrome->next; # skip 0
     $palindrome->next; # skip 3
     while (@got < $count) {
       my ($i, $value) = $palindrome->next;
       my $zeck = $seq->ith($value);
       ### $value
       ### zeck: sprintf '%b', $zeck
       if ($palindrome->pred($zeck)) {
         push @got, $value;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A094202 -- palindrome in Zeckendorf base

MyOEIS::compare_values
  (anum => 'A094202',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Palindromes;
     my $palindrome = Math::NumSeq::Palindromes->new (radix => 2);
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       if ($palindrome->pred($value)) {
         push @got, $i;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A095730 -- primes which are palindromes in Zeckendorf base

MyOEIS::compare_values
  (anum => 'A095730',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Palindromes;
     my $palindrome = Math::NumSeq::Palindromes->new (radix => 2);
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       if (is_prime($i) && $palindrome->pred($value)) {
         push @got, $i;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A210619 -- n equal number of 1-bits and 0-bits in Zeckendorf base
#
# Permutations of 101010...10 which start with a 1, means position "00" at
# successively lower positions.

MyOEIS::compare_values
  (anum => 'A210619',
   # max_count => 20,
   func => sub {
     my ($count) = @_;

     require Math::BigInt;
     require Math::Permute::Array;
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     for (my $c = 1; @got < $count; $c++) {
       my $str = ('10' x ($c-1)) . '1';
       ### $str
       my @i;
       for (my $pos = 1; $pos <= length($str); $pos += 2) {
         my $v = $str;
         substr($v,$pos,0) = '0';
         ### at: "pos=$pos  v=$v"
         $v = Math::BigInt->from_bin($v);
         push @i, $seq->value_to_i_floor($v);
       }
       @i = sort {$a<=>$b} @i;
       ### i: scalar(@i)
       while (@i && @got < $count) {
         push @got, shift @i;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A123740 -- characteristic of Wythoff AB,
#   is second lowest bit of Zeck(n) -- or rather of n-1

MyOEIS::compare_values
  (anum => 'A123740',
   func => sub {
     my ($count) = @_;
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     for (my $n = 1; @got < $count; $n++) {
       my $value = $seq->ith($n-1);
       push @got, ($value >> 1) & 1;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A134860 -- Wythoff AAB numbers is Zeck ending 101
# etc

foreach my $elem (
                  # Wythoff AB
                  # = Zeck ..10 plus 1
                  # = Zeck ..00 with even trailing 0s
                  # position of "1,0,1" pairs of 1s in Fibonacci word
                  ['A003623',  4, 2, offset=>1 ],
                  ['A003623',  4, 0, trailing0s => 0 ],

                  ['A134859',  8, 1],       # AAA
                  ['A134860',  8, 5],       # AAB
                  ['A134861', 16, 2],       # BAA
                  ['A134862', 16, 10, offset => 1 ],  # ABB
                  ['A134863', 16, 10],      # BAB
                  ['A134864', 32, 21, offset=>1 ],  # BBB
                  ['A151915', 16, 1],       # AAAA
                 ) {
  my ($anum, $modulus, $remainder, %option) = @$elem;
  MyOEIS::compare_values
      (anum => $anum,
       func => sub {
         my ($count) = @_;
         my $seq  = Math::NumSeq::Fibbinary->new;
         my @got;
         while (@got < $count) {
           my ($i, $value) = $seq->next;
           next if $value % $modulus != $remainder;
           if (defined $option{'trailing0s'}) {
             next unless $value;
             next if count_low_0_bits($value) % 2 != $option{'trailing0s'};
           }
           push @got, $i + ($option{'offset'}||0);
         }
         return \@got;
       });
}

sub count_low_0_bits {
  my ($n) = @_;
  if ($n <= 0) {
    return 0;
  }
  my $count = 0;
  until ($n % 2) {
    $count++;
    $n /= 2;
  }
  return $count;
}

#------------------------------------------------------------------------------
# A014417 -- n in fibonacci base, the fibbinaries written out in binary

MyOEIS::compare_values
  (anum => 'A014417',
   func => sub {
     my ($count) = @_;
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       push @got, sprintf '%b', $value;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A102364 -- num terms not used, how many zero bits

MyOEIS::compare_values
  (anum => 'A102364',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::DigitCount;
     my $cnt = Math::NumSeq::DigitCount->new (digit => 0, radix => 2);
     my $seq = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       push @got, $cnt->ith($value);
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A104324 -- how many bit runs, starting from n=1

MyOEIS::compare_values
  (anum => 'A104324',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     $seq->seek_to_i(1);
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       push @got, count_bit_runs($value);
     }
     return \@got;
   });

sub count_bit_runs {
  my ($n) = @_;
  my $count = 0;
  while ($n) {
    if ($n & 1) {
      do {
        $n >>= 1;
      } while ($n & 1);
      $count++;
    } else {
      do {
        $n >>= 1;
      } until ($n & 1);
      $count++;
    }
  }
  return $count;
}

#------------------------------------------------------------------------------
# A182636 Numbers whose Wythoff representation has odd length.
# A182637 Numbers whose Wythoff representation has even length.
# Zeck -> Wyth by 01 -> 1

MyOEIS::compare_values
  (anum => 'A182636',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       my $str = sprintf '%b', $value;
       $str =~ s/01/1/g;
       if (length($str) % 2 == 1) {
         push @got, $i+1;
       }
     }
     return \@got;
   });

MyOEIS::compare_values
  (anum => 'A182637',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       my $str = sprintf '%b', $value;
       $str =~ s/01/1/g;
       if (length($str) % 2 == 0) {
         push @got, $i+1;
       }
     }
     return \@got;
   });


#------------------------------------------------------------------------------
# A118113 - 2*fibbinary+1

MyOEIS::compare_values
  (anum => 'A118113',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       push @got, 2*$value+1;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A003622 - odd Zeckendorfs

MyOEIS::compare_values
  (anum => 'A003622',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       if ($value % 2) {
         push @got, $i;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A022342 - Zeckendorf even, i where value is even
#           floor(n*phi)-1
#           "Fibonacci successor"

MyOEIS::compare_values
  (anum => 'A022342',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       if (($value % 2) == 0) {
         push @got, $i;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A035514 - Zeckendorf fibonnacis concatenated as decimal digits, high to low

MyOEIS::compare_values
  (anum => 'A035514',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Fibonacci;
     my $fibonacci  = Math::NumSeq::Fibonacci->new;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       my $concat = '';
       my $pos = 0;
       while ($value) {
         if ($value & 1) {
           $concat = $fibonacci->ith($pos+2) . $concat;
         }
         $value >>= 1;
         $pos++;
       }
       push @got, $concat || 0;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A035515 - Zeckendorf fibonnacis concatenated as decimal digits, low to high

MyOEIS::compare_values
  (anum => 'A035515',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Fibonacci;
     my $fibonacci  = Math::NumSeq::Fibonacci->new;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       my $concat = '';
       my $pos = 0;
       while ($value) {
         if ($value & 1) {
           $concat .= $fibonacci->ith($pos+2);
         }
         $value >>= 1;
         $pos++;
       }
       push @got, $concat || 0;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A035516 - Zeckendorf fibonnacis of each n, high to low
#           with single 0 for n=0

MyOEIS::compare_values
  (anum => 'A035516',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Fibonacci;
     require Math::NumSeq::Repdigits;
     my $fibonacci  = Math::NumSeq::Fibonacci->new;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
   OUTER: for (;;) {
       my ($i, $value) = $seq->next;
       if ($value) {
         my @bits = Math::NumSeq::Repdigits::_digit_split_lowtohigh($value,2);
         foreach my $pos (reverse 0 .. $#bits) {
           if ($bits[$pos]) {
             push @got, $fibonacci->ith($pos+2);
             last OUTER if @got >= $count;
           }
         }
       } else {
         push @got, 0;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A035517 - Zeckendorf fibonnacis of each n, low to high
#           with single 0 for n=0

MyOEIS::compare_values
  (anum => 'A035517',
   func => sub {
     my ($count) = @_;
     require Math::NumSeq::Fibonacci;
     require Math::NumSeq::Repdigits;
     my $fibonacci  = Math::NumSeq::Fibonacci->new;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
   OUTER: for (;;) {
       my ($i, $value) = $seq->next;
       if ($value) {
         my @bits = Math::NumSeq::Repdigits::_digit_split_lowtohigh($value,2);
         foreach my $pos (0 .. $#bits) {
           if ($bits[$pos]) {
             push @got, $fibonacci->ith($pos+2);
             last OUTER if @got >= $count;
           }
         }
       } else {
         push @got, 0;
       }
     }
     return \@got;
   });

#------------------------------------------------------------------------------
# A048678 - binary expand 1->01, so no adjacent 1 bits
# is a permutation of the fibbinary numbers

MyOEIS::compare_values
  (anum => 'A048678',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     for (my $n = 0; @got < $count; $n++) {
       push @got, expand_1_to_01($n);
     }
     return \@got;
   });

sub expand_1_to_01 {
  my ($n) = @_;
  my $bits = digit_split($n,2);  # $bits->[0] low bit
  @$bits = map {$_==0 ? (0) : (1,0)} @$bits;
  return digit_join($bits,2);
}

sub digit_split {
  my ($n, $radix) = @_;
  ### _digit_split(): $n

  if ($n == 0) {
    return [ 0 ];
  }
  my @ret;
  while ($n) {
    push @ret, $n % $radix;  # ret[0] low digit
    $n = int($n/$radix);
  }
  return \@ret;
}

sub digit_join {
  my ($aref, $radix) = @_;
  ### digit_join(): $aref

  my $n = 0;
  foreach my $digit (reverse @$aref) {  # high to low
    $n *= $radix;
    $n += $digit;
  }
  return $n;
}


#------------------------------------------------------------------------------
# A048679 - compressed Fibbinary mapping 01->1
# (permutation of the integers)

MyOEIS::compare_values
  (anum => 'A048679',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     while (@got < $count) {
       my ($i, $value) = $seq->next;
       push @got, compress_01_to_1($value);
     }
     return \@got;
   });

# also delete one 0 from each run of 0s
sub compress_01_to_1 {
  my ($value) = @_;
  my $ret = $value * 0; # inherit bignum
  my $retbit = 1;
  while ($value) {
    my $bits = $value & 3;
    if ($bits == 0 || $bits == 2) {
      $value >>= 1;
    } elsif ($bits == 1) {
      $ret += $retbit;
      $value >>= 2;
    } else {
      die "Oops compress_01 bits 11";
    }
    $retbit <<= 1;
  }
  return $ret;
}


#------------------------------------------------------------------------------
# A048680 - binary expand 1->01, then fibbinary index of that value
# (permutation of the integers)

MyOEIS::compare_values
  (anum => 'A048680',
   func => sub {
     my ($count) = @_;
     my $seq  = Math::NumSeq::Fibbinary->new;
     my @got;
     for (my $n = 0; @got < $count; $n++) {
       my $expand = expand_1_to_01($n);
       my $fib_i = $seq->value_to_i_floor($expand);
       push @got, $fib_i;
     }
     return \@got;
   });

#------------------------------------------------------------------------------
exit 0;
