#!/opt/bin/perl -w
# I read the problem description and immediately thought object, so a Frost
# object was born. The output is created with Imager. One oddity is that
# the y coordinate in the first index in the array of arrays - it just made
# it easier to print to the screen.
# 
# Some images can be found at http://www.sainio.org/qotw/7/.
# 
# Garth
# 

use strict;

# Initialization parameters
my $x_width = 100;
my $y_width = 100;
my $humidity = 20;

# create
my $frost = new Frost($x_width, $y_width, $humidity);

# simulate
$SIG{ALRM} = sub { warn "TICKS: $frost->{tick}\n"; exit };
alarm $ENV{TIMELIMIT} || 120;
$frost->simulate;

package Frost;
###############################################################################
# Package implementing the Frost simulation as described in Expert QOTW #7
#
# ' ' => vacuum
# '.' => vapor
# '*' => ice
#
# The coordinate system follows the "standard" display system of (0,0)
# being in the upper left corner.
###############################################################################
use strict;
use Imager;
use Imager::Color;
use constant PRINT_TICK => 1; # print every so many ticks

###############################################################################
# X and Y coordinates must be even and greater than 0
# the humidity defaults to 20
###############################################################################
sub new {
    my($class, $x_width, $y_width, $humidity) = @_;

    $humidity ||= 20;

    die("Sizes must be at least 2.\n") unless($x_width > 1 && $y_width > 1);
    die("Sizes must be even.\n") unless($x_width%2 == 0 && $y_width%2 == 0);
    die("Humidty must be in the 1-99 range.\n") unless($humidity > 0 &&
                                                       $humidity < 100);



    my $self = {};

    $self->{'x_width'} = $x_width;
    $self->{'y_width'} = $y_width;
    $self->{'grid'} = [];
    $self->{'tick'} = 0;
    $self->{'humidity'} = $humidity;

    bless($self, $class);

    $self->initialize;
    return $self;
}

###############################################################################
# Initialize changes $humidity percent of the vacuum to vapor and picks
# a random starting point for the ice crystal.
###############################################################################
sub initialize {
    my($self) = @_;

    # Initialize everything to vacuum
    for my $y (0..$self->{'y_width'}-1) {
        for my $x (0..$self->{'x_width'}-1) {
            $self->{'grid'}->[$y]->[$x] = ' ';
        }
    }

    # Determine the number of cells that should be initialized to vapor
    my $vapor_cells = int($self->{'humidity'}/100 * $self->{'x_width'} * $self->{'y_width'});
    # There must be at least one vapor cell
    $vapor_cells ||= 1;

    my $set_vapor = 0;

    # Randomly change vacuum cells to vapor until $vapor_cells of vapor
    # are present
    while($set_vapor < $vapor_cells) {
        my $x = int(rand($self->{'x_width'}));
        my $y = int(rand($self->{'y_width'}));
        if($self->{'grid'}->[$y]->[$x] eq ' ') {
            $self->{'grid'}->[$y]->[$x] = ".";
            $set_vapor++;
        }
    }

    # set one cell to be the ice particle
    my $x = int(rand($self->{'x_width'}));
    my $y = int(rand($self->{'y_width'}));
    $vapor_cells-- if($self->{'grid'}->[$y]->[$x] eq '.');
    $self->{'vapor'} = $vapor_cells;
    $self->{'grid'}->[$y]->[$x] = "*";
}

###############################################################################
# Simulate the ticks
###############################################################################
sub simulate {
    my($self) = @_;

    while(1) {
        # end if there is no more vapor left
        last if($self->{'vapor'} <= 0);

        # $self->_print;# if($self->{'tick'} % PRINT_TICK == 0);
        $self->_createImage if($self->{'tick'} % PRINT_TICK == 0);

        # Even ticks start dividing the neighbourhoods based on 0,0
        # Odd ticks start at 1,1
        my ($x_origin, $y_origin) = (0,0);
        if($self->{'tick'}%2 == 1) {
            ($x_origin, $y_origin) = (1,1);
        }

        # Go through all the neighborhoods and update the contents. The updates
        # can happen in place since the contents of one neighborhood only
        # depend on the cells in that neighborhood.
        while($x_origin < $self->{'x_width'}) {
            while($y_origin < $self->{'y_width'}) {
                # Wrap for odd ticks when x or y is the max index
                my $x1 = $x_origin;
                my $y1 = $y_origin;
                my $x2 = ($x_origin + 1) % $self->{'x_width'};
                my $y2 = ($y_origin + 1) % $self->{'y_width'};

                my @ice;
                my @vapor;

                foreach my $y ($y1, $y2) {
                    foreach my $x ($x1, $x2) {
                        if($self->{'grid'}->[$y]->[$x] eq '*') {
                            push(@ice, [$x, $y]);
                        }elsif($self->{'grid'}->[$y]->[$x] eq '.') {
                            push(@vapor, [$x, $y]);
                        }
                    }
                }

                # If there is any ice, turn any vapor into ice
                # keep track of how much vapor is left
                if(scalar(@ice) > 0) {
                    foreach my $v (@vapor) {
                        $self->{'grid'}->[$v->[1]]->[$v->[0]] = "*";
                        $self->{'vapor'}--;
                    }
                } else {
                    # Rotate the elements
                    # choices are
                    #             Clockwise || CounterClockwise
                    # (x1, y1) => (x2, y1)  || (x1, y2)
                    # (x2, y1) => (x2, y2)  || (x1, y1)
                    # (x2, y2) => (x1, y2)  || (x2, y1)
                    # (x1, y2) => (x1, y1)  || (x2, y2)

                    # Store in temp variable
                    my $x1y1 = $self->{'grid'}->[$y1]->[$x1];
                    my $x2y1 = $self->{'grid'}->[$y1]->[$x2];
                    my $x2y2 = $self->{'grid'}->[$y2]->[$x2];
                    my $x1y2 = $self->{'grid'}->[$y2]->[$x1];

                    # random number < 0.5 = clockwise, else counter clockwise
                    if(rand() < 0.5) {
                        $self->{'grid'}->[$y1]->[$x2] = $x1y1;
                        $self->{'grid'}->[$y2]->[$x2] = $x2y1;
                        $self->{'grid'}->[$y2]->[$x1] = $x2y2;
                        $self->{'grid'}->[$y1]->[$x1] = $x1y2;
                    } else {
                        $self->{'grid'}->[$y2]->[$x1] = $x1y1;
                        $self->{'grid'}->[$y1]->[$x1] = $x2y1;
                        $self->{'grid'}->[$y1]->[$x2] = $x2y2;
                        $self->{'grid'}->[$y2]->[$x2] = $x1y2;
                    }
                }

                $y_origin +=2;
            }
            # reset the y_origin for the next column of neighborhoods
            $y_origin = 0 + $self->{'tick'}%2;
            $x_origin +=2;
        }
        $self->{'tick'}++;
    }
    # $self->_print;
    $self->_createImage;
}

###############################################################################
# Print the grid
###############################################################################
sub _print {
    my($self) = @_;
    print "T=", $self->{'tick'}, "\n";
    # print "-" x $self->{'x_width'}, "\n";
    print " ";
    for my $i (0..$self->{'x_width'}) {
        print $i%10;
    }
    print "\n";
    my $rc = 0;
    foreach my $row (@{$self->{'grid'}}) {
        print $rc%10,  @$row, "\n";
        $rc++;
    }
    print "-" x $self->{'x_width'}, "\n";
}

###############################################################################
# print the contents of the grid to an image
###############################################################################
sub _createImage {
    my($self) = @_;
    # filename is frost_$tick.ping
    # the new image defaults to a black background
    my $image = Imager->new(xsize => $self->{'x_width'},
                            ysize => $self->{'y_width'},
                            type => 'png');
    # Create the colors needed
    my $vapor = Imager::Color->new(0, 0, 255); # blue
    my $ice = Imager::Color->new(255, 255, 255); #white

    # Print the contents of the grid to the image. one cell == one pixel.
    for my $x (0..$self->{'x_width'} -1 ) {
        for my $y (0..$self->{'y_width'}-1) {
            if($self->{'grid'}->[$y]->[$x] eq '*') {
                # ice
                $image->setpixel('x' => $x, 'y' => $y, 'color' =>$ice);
            } elsif($self->{'grid'}->[$y]->[$x] eq '.') {
                # vapor
                $image->setpixel('x' => $x, 'y' => $y, 'color' =>$vapor);
            }
        }
    }
    $image->write('file'=>'frost_' . sprintf("%05d", $self->{'tick'}) . '.png');
    warn "Created image ($self->{tick})\n";
}

1;


