#!/usr/bin/perl

# I also enjoyed this quiz a lot.  It seemed to me that there was going to
# be quite a lot of global state in this one, so i implemented it as an
# object.
# 
# I called it Andrew::Frost, just to be original.  It has two
# constructors.  The first, called new, takes two mandatory parameters and
# one optional one.  The mandatory parameters are the width and height of
# the grid.  The optional parameter is the percentage of the grid to fill
# with vapour, if not provided it defaults to 50.  This constructs a
# random starting grid and places a single ice crystal in the middle.
# 
# The second constructor is called from_file and takes a single parameter,
# the name of the file containing the data to build the grid.  The routine
# pads all the lines to the same length as the longest line.  It does a
# verification that the grid is a sensible size, but doesn't check the
# data you pass it.
# 
# If either of the constructors ends up with an odd number of rows or
# columns, it dies.
# 
# Each call to next_iteration does one click. To see the state of the
# object at any point call the paint method, this returns an ascii art
# representation of the object. I put in the separators that Mark used in
# the specification of the problem. It has two possible outputs: one per
# the split on even iterations and one per the split on odd iterations. By
# default it will always print the even representation, you can get it to
# print the other one by passing the string odd to the paint method. If
# you want it to print a representation of the way this iteration was
# split, pass a true value to the alternate method.
# 
# Internally, the processing of the array is done using the next and
# transform methods. The next method sets up four scalar attributes to
# point to the cells to be worked with. The transform method then
# manipulates their contents using the get_points and set points methods.
# The next method returns true until it has stepped through the entire
# array at which point it starts to return undef. The routine will
# continue to return undef until the reset method is called which sets up
# the object for the next iteration.
# 
# Here's a little script that uses the object:
# 

use strict;
use warnings;
use Data::Denter;

my $obj = Andrew::Frost->new(50,20,23);

$obj->alternate(1);
$obj->reset;
print Denter $obj->paint;

for (0 .. 20) {
  $obj->next_state;
  $obj->next_state;
  $obj->next_state;
  $obj->next_state;
  $obj->next_state;
  print Denter $obj->paint;
}

package Andrew::Pure::Frost;

use strict;
use warnings;
use base qw(Class::Accessor);

BEGIN {
Andrew::Pure::Frost->mk_ro_accessors(qw(width height));
Andrew::Pure::Frost->mk_accessors(qw(alternate
                                     cur_width
                                     cur_height
                                     ul ur bl br
                                     odd rows));
}

package Andrew::Frost;

use Carp;
use base qw(Andrew::Pure::Frost);
use Data::Denter;

sub new {
  my ($class, $width, $height, $percent) = @_;

  croak 'No size given for grid' unless defined $width and defined $height;
  croak "width ($width) is not an even integer" if $width % 2;
  croak "height ($height) if not an even integer" if $height % 2;

  defined $percent or $percent = 50;
  
  my @rows;
  for (1 .. $height) {
    push @rows, [map {rand(100) <= $percent ? '.' : ' '} (1 .. $width)];
  }

  $rows[$height/2][$width/2] = '*';

  return $class->_init($width, $height, \@rows);
}

sub from_file {
  my ($class, $file) = @_;

  -r $file or return undef;
  my $fh;
  open($fh, "<$file") or die "Can't open $file $!\n";
  my @lines = <$fh>;

  die "number of lines in $file is not even\n" if @lines % 2; 

  chomp @lines;
  my ($max, @data) = 0;
  for (@lines) {
    $max = length $_ if length $_ > $max;
    push @data, [split "", $_];
  }
  
  die "length of lines is $file is not even\n" if $max % 2; 

  for my $row (@data) {
    push @$row, ' ' while @$row < $max;
  }
  return $class->_init($max, scalar @data, \@data);
}

sub _init {
  my ($class, $width, $height, $rows) = @_;
  my $self = $class->SUPER::new({width  => $width,
                                 height => $height});
  bless $self, $class;

  $self->rows($rows);
  $self->cur_width(-2);
  $self->cur_height(0);
  $self->odd(1);
  $self->alternate(0);
  return $self;
}

sub next {
  my $self = shift;
  
  $self->cur_width($self->cur_width + 2);
  if ($self->cur_width >= $self->width) {
    $self->cur_width(0);
    $self->cur_height($self->cur_height + 2);
  } 
  return undef if $self->cur_height >= $self->height;

  my $width1  = $self->cur_width + $self->odd;
  my $height1 = $self->cur_height + $self->odd;
  my $width2  = ($width1  + 1) % $self->width;
  my $height2 = ($height1 + 1) % $self->height;

  $self->ul(\$self->rows->[$height1][$width1]);
  $self->ur(\$self->rows->[$height1][$width2]);
  $self->br(\$self->rows->[$height2][$width2]);
  $self->bl(\$self->rows->[$height2][$width1]);
  return 1;
}

sub reset {
  my $self = shift;

  $self->cur_width(-2);
  $self->cur_height(0);
  $self->odd(($self->odd + 1) % 2);
}

sub next_state {
  my $self = shift;
  $self->reset;
  $self->transform while ($self->next);
}

sub get_points { 
  my $self = shift;
  (${$self->ul}, ${$self->ur}, ${$self->br}, ${$self->bl});
}

sub set_points {
  my ($self, $ul, $ur, $br, $bl) = @_;
  ${$self->ul} = $ul;
  ${$self->ur} = $ur; 
  ${$self->bl} = $bl; 
  ${$self->br} = $br;
}

sub transform {
  my $self = shift;

  my ($ice) = grep {$_ eq '*'} $self->get_points;
  if (defined $ice) {
    $self->set_points(map {tr/./*/;$_} $self->get_points);
    return;
  }

  my @points = $self->get_points;
  if (int rand(2)) {
    my $temp = shift @points;
    push @points, $temp; 
  } else { 
    my $temp = pop @points;
    unshift @points, $temp; 
  }
  $self->set_points(@points);
}

sub paint {
  my ($self, $type) = @_;

  if (defined $type) {
    return $type eq 'odd' ? $self->paint_odd : $self->paint_even;
  } elsif ($self->alternate) {
    return $self->odd ? $self->paint_odd : $self->paint_even;
  } else {
    $self->paint_even;
  }
}

sub paint_even {
  my ($self) = @_;

  my $separator = '+--' x ($self->width / 2);
  $separator .= '+';

  my @interim; 
  for my $row (@{$self->rows}) {
    my $build;
    my @row = (@{$row});
    while (@row) {
      my $one = shift @row;
      my $two = shift @row;
      $build .= "|$one$two";
    }
    push @interim, $build.'|';;
  }

  my $pic;
  while (@interim) {
    my $one = shift @interim;
    my $two = shift @interim;
    $pic .= "$separator\n$one\n$two\n";
  }
  $pic .= "$separator\n";
  $pic;
}

sub paint_odd {
  my ($self, $type) = @_;

  my $separator = '-+-' x ($self->width / 2);

  my @interim; 
  for my $row (@{$self->rows}) {
    my $build;
    my @row = (@{$row});
    while (@row) {
      my $one = shift @row;
      my $two = shift @row;
      $build .= "$one|$two";
    }
    push @interim, $build;
  }

  my $pic;
  while (@interim) {
    my $one = shift @interim;
    my $two = shift @interim;
    $pic .= "$one\n$separator\n$two\n";
  }
  $pic;
}

1;


