#!/usr/bin/perl

use strict;
use warnings;

use Getopt::Long;

#This will let me handle the output as a simple matrix.

# A simple screen abstraction, actually a long array
package Screen;
sub new { # Get a Screen of given sizes
   my ($class, $wx, $wy) = @_;
   my $self = {_wx => $wx,
               _wy => $wy,
               _data => [ ((' ') x $wx , "\n") x $wy ]};
   bless $self, $class;
}

sub _index { # Find index of given coordinate into vector
	my ($self, $x, $y) = @_;
	return $x + ($y * (1 + $self->{_wx}));
}

# Public methods
sub inbound { # Test if coordinates are within Screen bounds
   my ($self, $x, $y) = @_;
   return ($x >= 0 && $y >= 0 && $x < $self->{_wx} && $y < $self->{_wy});
}

sub set { # Set value $v at given coordinates, with clipping
   my ($self, $x, $y, $v) = @_;
   return undef unless $self->inbound($x, $y);
   $self->{_data}->[$self->_index($x, $y)] = $v;
}

sub to_string { # Dump Screen onto string
	return join('', @{$_[0]->{_data}});
}

#Back to reality. The main cycle gets a new ASCII clock representation every second and prints it.

package main;
use POSIX 'ceil';

my $once;
my $noseconds;
my $ofile;

my $result = GetOptions (
    "once" => \$once,
    "noseconds" => \$noseconds,
	"ofile=s" => \$ofile
    );  # flag


my $pi2 = 2 * atan2(0, -1); # Useful to have around

my ($wx, $wy) = @ARGV; # Accept sizes from command line
my $breaker;

while (! $breaker) { # Cycle forever, updating each second
	if ($ofile) {
		open OFILE, ">$ofile" or die "Hey, I can't open $ofile!!";
	   	print OFILE anaclock($wx, $wy);
		close OFILE;
	}
	else {
		print "\n", anaclock($wx, $wy);
	}
   	sleep(1);
   	if ($once) {$breaker++;};
	if ($ofile) {
		close OFILE;
		open OFILE, ">$ofile" or die "I can't open that file: $ofile !!"
	} 	
}

#Accessor functions. The adjust function rounds numbers to have integer matrix indexes; in the meanwhile, it also performs a translation by 1 cell - please don't ask me why, it works :)

#The two drawing functions do what they declare.

# Round and translate to better fit inside the screen
sub adjust {return ceil($_[0] - 1.5);}

sub draw_circle {
   my ($screen, $cx, $cy, $rx, $ry, $c) = @_;
   my $radius = ($rx > $ry) ? $rx : $ry;
   my $step = abs(1 / ($radius * $pi2));
   for (my $a = 0.0; $a < 2 * $pi2; $a += $step) {
      $screen->set(adjust($cx + $rx * cos($a)), 
                   adjust($cy + $ry * sin($a)), $c);
   }
}

sub draw_line {
   my ($screen, $ax, $ay, $bx, $by, $c) = @_;
   my ($dx, $dy) = ($bx - $ax, $by - $ay);
   my ($adx, $ady) = (abs($dx), abs($dy));
   my $delta = ($adx > $ady) ? $adx : $ady;
   $dx /= $delta;
   $dy /= $delta;
   for (; $delta > 0; $ax += $dx, $ay += $dy, --$delta) {
      $screen->set(adjust($ax), adjust($ay), $c);
   }
}

#The actual ASCII clock building function. It accepts width and heigth even if it increases them by 1 when building the screen- I hope you're really not going to be annoyed because of this.

sub anaclock {
   my $width  = shift || 44;
   my $height = shift || 22;

   my ($rx, $ry) = ($width / 2, $height / 2);
   my ($cx, $cy) = (1 + $rx, 1 + $ry);

   # Get a virtual screen to write onto; get it a little larger
	# to cope with roundups
   my $screen = Screen->new($width + 1, $height + 1);

   # Draw the surronding circle
   draw_circle($screen, $cx, $cy, $rx, $ry, '.');

   # Decrease radius to be strictly inside
   $rx *= 6/8;
   $ry *= 6/8;

   # What time is it?
   my ($sec, $min, $hour) = (localtime(time))[0 .. 2];

   # Scale values to get angles. Note that $min is shifted by the
   # seconds, and $hour is shifted by the minutes.
   $sec  *= $pi2 / 60;
   $min  = ($min * $pi2 + $sec) / 60;
   $hour = (($hour % 12) * $pi2 + $min) / 12;
   
   # Draw lines. According to most clocks, hours are in background and
	# seconds in foreground.
   draw_line($screen, $cx, $cy, $cx + $rx * sin($hour) * 2 / 3,
      $cy - $ry * cos($hour) * 2 / 3, '#');
   draw_line($screen, $cx, $cy, $cx + $rx * sin($min), 
      $cy - $ry * cos($min), '+');
   unless ($noseconds)
       { draw_line($screen, $cx, $cy, $cx + $rx * sin($sec),  
		   $cy - $ry * cos($sec), '.'); }

   # Return a string representation
   return $screen->to_string();
}
