# Copyright 2015 Kevin Ryde
#
# This file 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.
#
# This file 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 this file.  See the file COPYING.  If not, see
# <http://www.gnu.org/licenses/>.

package Graph::Maker::Hanoi;
use 5.004;
use strict;
use base 'Graph::Maker';

use vars '$VERSION';
$VERSION = 4;

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

sub _default_graph_maker {
  require Graph;
  Graph->new(@_);
}

sub init {
  my ($self, %params) = @_;
  my $N = delete($params{'N'}) || 0;

  my $graph_maker = delete($params{'graph_maker'});
  $graph_maker ||= \&_default_graph_maker;
  my $graph = $graph_maker->(%params);

  $graph->set_graph_attribute (name => "Hanoi $N");
  my $directed = $graph->is_directed;

  {
    if ($N <= 0) {
      $graph->add_vertex(0);
    } else {
      my @t = (0) x $N;
      for (;;) {
        my $v = join('',@t);
        my @t2 = @t;
        foreach (1, 2) {
          $t2[-1]++;
          $t2[-1] %= 3;
          if ($directed || $t2[-1] > $t[-1]) {
            my $v2 = join('',@t2);
            ### smallest disc: "$v to $v2"
            $graph->add_edge($v, $v2);
          }
        }

        for (my $pos = $#t; $pos >= 0; $pos--) {
          if ($t[$pos] != $t[-1]) {
            @t2 = @t;
            $t2[$pos]++;
            $t2[$pos] %= 3;
            if ($t2[$pos] == $t[-1]) {
              $t2[$pos]++;
              $t2[$pos] %= 3;
            }
            if ($directed || $t2[$pos] > $t[$pos]) {
              my $v2 = join('',@t2);
              ### second disc: "$v to $v2"
              $graph->add_edge($v, $v2);
            }
          }
        }

        # increment ...
        my $pos = $#t;
        for (;;) {
          last if ++$t[$pos] < 3;
          $t[$pos] = 0;
          if (--$pos < 0) {
            return $graph;
          }
        }
      }
    }
  }
  {
    if ($N <= 0) {
      $graph->add_vertex(0);
    } else {
      my $v_max = 3**$N - 1;
      my $vpad_max = 3**($N-1) - 1;
      ### $N
      ### $v_max
      ### $vpad_max

      foreach my $v (0 .. $v_max) {
        my $low = $v % 3;
        ### $v

        foreach my $inc (1, 2) {
          ### $low
          ### $inc
          my $other = ($low + $inc) % 3;
          {
            my $v2 = $v - $low + $other;
            if ($directed || $v2 > $v) {
              ### smallest disc: "$v to $v2"
              $graph->add_edge($v, $v2);
            }
          }

          ### $low
          ### $other
          my $pad = ($low - $inc) % 3;
          my $mod = 3;
          my $rem = $low;
          foreach (1 .. $N) {
            $mod *= 3;
            $rem = 3*$rem + $low;
            my $got = $v % $mod;
            ### $mod
            ### $rem
            ### $got
            if ($got != $rem) {
              my $v2 = $v - $got + ((2*$got - $rem) % $mod);
              if ($directed || $v2 > $v) {
                ### second smallest: "$v to $v2"
                $graph->add_edge($v, $v2);
              }
              last;
            }
          }

          # my $pad = ($low - $inc) % 3;
          # ### $other
          # ### $pad
          #
          # my $vpad = $v;
          # for (;;) {
          #   ### at: "vpad=$vpad  v2=$v2"
          #   last if $vpad >= $vpad_max || $v2 >= $vpad_max;
          #   $vpad = 3*$vpad + $pad;
          #   $v2   = 3*$v2 + $pad;
          #   if ($directed || $v2 > $vpad) {
          #     ### second smallest: "$vpad to $v2"
          #   }
          #     $graph->add_edge($vpad, $v2);
          # }
        }
      }
    }
    return $graph;
  }
}

  Graph::Maker->add_factory_type('hanoi' => __PACKAGE__);
1;

__END__

=for stopwords Ryde

=head1 NAME

Graph::Maker::Hanoi - create Hanoi graph

=for test_synopsis my ($graph)

=head1 SYNOPSIS

 use Graph::Maker::Hanoi;
 $graph = Graph::Maker->new ('hanoi', N => 8, K => 3);

=head1 DESCRIPTION

C<Graph::Maker::Hanoi> creates C<Graph.pm> graphs of towers of Hanoi puzzle
configurations.  This is equivalent to adjacent points of the Sierpinski
triangle.

                       
                       
      *  N=1          *                       *              
     / \             / \   N=2               / \
    *---*           *---*                   *---*     N=3                  
                   /     \                 /     \              
                  *       *               *       *             
                 / \     / \             / \     / \            
                *---*---*---*           *---*---*---*           
                                       /             \       
                                      *               *      
                                     / \             / \     
                                    *---*           *---*    
                                   /     \         /     \   
                                  *       *       *       *  
                                 / \     / \     / \     / \ 
                                *---*---*---*---*---*---*---*
    
The towers of Hanoi puzzle has three spindles with discs of increasing size.
Each vertex is a configuration of discs on spindles.  Each edge is a legal
move, being a move of a single disc to another spindle, preserving the
property that all discs are increasing size.

At each configuration there are three legal moves.  The smallest disc can be
moved to either of the other two spindles, or the smaller disc on those
other two spindles can swap.

=cut

# Configurations are represented as integers 0 to 3^N-1.  Each ternary digit
# represents a disc and its value 0,1,2 is which spindle holds that disc.  The
# legal moves are to change the least significant digit to either of its two
# other values, which is a move of the smallest disc.  Or the lowest digit
# different from that least significant can be changed to the other value (not
# itself and not the least significant).  This is a move between the spindles
# not holding the smallest disc.  The smallest disc on those two spindles is
# moved to the other.

=pod

=head1 FUNCTIONS

=over

=item C<$graph = Graph::Maker-E<gt>new('hanoi', key =E<gt> value, ...)>

The key/value parameters are

    N   =>  integer

Other parameters are passed to C<Graph-E<gt>new()>.

=back

=head1 SEE ALSO

L<Graph::Maker>

=head1 LICENSE

Copyright 2015 Kevin Ryde

This file 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.

This file 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
This file.  If not, see L<http://www.gnu.org/licenses/>.

=cut
