package SVG::Calendar;

# Created on: 2006-04-22 10:36:43
# Create by:  ivan
# $Id$
# # $Revision$, $HeadURL$, $Date$
# # $Revision$, $Source$, $Date$

use strict;
use warnings;
use version;
use Carp;
use Scalar::Util qw/blessed/;
use Data::Dumper qw/Dumper/;
use Clone qw/clone/;
use Math::Trig;
use DateTime::Format::Strptime qw/strptime strftime/;
use Template;
use File::ShareDir qw/dist_dir/;
use Readonly;
use Image::ExifTool qw/ImageInfo/;
use English '-no_match_vars';
use base qw/Exporter/;

our $VERSION   = version->new('0.3.12');
our @EXPORT_OK = qw//;

Readonly my $MARGIN_RATIO             => 0.04;
Readonly my $DAY_COLS                 => 8;
Readonly my $ROUNDING_FACTOR          => 0.5;
Readonly my $TEXT_OFFSET_Y            => 0.1;
Readonly my $TEXT_OFFSET_X            => 0.15;
Readonly my $TEXT_WIDTH_RATIO         => 0.1;
Readonly my $TEXT_HEIGHT_RATIO        => 0.145;
Readonly my $MOON_SCALE_WIDTH         => 0.3;
Readonly my $MOON_SCALE_HEIGHT        => 0.8;
Readonly my $HEADING_WIDTH_SCALE      => 0.8;
Readonly my $HEADING_HEIGHT_SCALE     => 0.45;
Readonly my $HEADING_DOW_WIDTH_SCALE  => 2;
Readonly my $HEADING_DOW_HEIGHT_SCALE => 0.4;
Readonly my $HEADING_WOY_WIDTH_SCALE  => 4;
Readonly my $HEADING_WOY_HEIGHT_SCALE => 0.9;
Readonly my $MAX_WEEK_ROW             => 5;
Readonly my $MAX_DAYS                 => 42;
Readonly my $INTERVAL_ONE_DAY         => DateTime::Duration->new(days => 1);
Readonly my $INTERVAL_ONE_WEEK        => DateTime::Duration->new(days => 7);
Readonly my $INTERVAL_ONE_MONTH       => DateTime::Duration->new(months => 1);
Readonly my $INTERVAL_ELEVEN_MONTHS   => DateTime::Duration->new(months => 11);
Readonly my $FULL_MOON                => 100;
Readonly my $MOON_RADIAL_STEP         => 1.34;
Readonly my $MOON_AT_NIGHT            => DateTime::Duration->new(hours => 20);
Readonly my $FULL_CIRCLE_DEGREES      => 360;
Readonly my $ONE_WEEK                 => 7;

sub new {

    my ( $class, %param ) = @_;
    my $self = clone \%param;

    bless $self, $class;

    $self->init();

    return $self;
}

sub init {
    my $self    = shift;
    my %size    = $self->get_page();
    my %temp    = ( page => \%size, xu => $self->{page}{width_unit}, yu => $self->{page}{height_unit}, );
    my $height  = $self->{page}{height};
    my $width   = $self->{page}{width};
    my $xu      = $self->{page}{width_unit};
    my $yu      = $self->{page}{height_unit};
    my $xmargin = $self->{page}{margin} || $self->{page}{width} * $MARGIN_RATIO;
    my $ymargin = $self->{page}{margin} || $self->{page}{height} * $MARGIN_RATIO;
    $self->{page}{x_margin}    = $xmargin;
    $self->{page}{y_margin}    = $ymargin;
    $self->{moon}{xoffset}   ||= 0;
    $self->{moon}{yoffset}   ||= 0;
    $self->{calendar_height} ||= '0.5';
    $self->{calendar_height}  =~ s/%//exms;
    if ( $self->{calendar_height} > 1 ) {
        # assume that the height is a percentage value and divide by 100
        $self->{calendar_height} /= 100;
    }
    $self->{classes} = {};

    # cal bounding box (bb)
    $temp{bb} = {
        x      => $xmargin,
        y      => ( $height * ( 1 - $self->{calendar_height} ) + $ymargin ),
        height => ( $height * $self->{calendar_height} - $ymargin * 2 ),
        width  => ( $width - $xmargin * 2 ),
    };

    my $rows              = $MAX_WEEK_ROW + 1;
    my $row_height        = $temp{bb}{height} / ( $rows + $ROUNDING_FACTOR ) * ( 0.5 + $self->{calendar_height} );
    my $row_margin_height = $row_height / ( $rows * 2 );
    my $cols              = $DAY_COLS;
    my $col_width         = $temp{bb}{width} / ( $cols + $ROUNDING_FACTOR );
    my $col_margin_width  = $col_width / ( $cols * 2 );

    # setup the day boxes row by row
    for my $i ( 2 .. $rows ) {
        my $row_y = $temp{bb}{y} + $row_margin_height * ( 2 * $i - 1 ) + $row_height * ( $i - 1 );

        # setup the individual days
        for my $j ( 2 .. $cols ) {
            my $x = ( $temp{bb}{x} + $col_margin_width * ( 2 * $j - 1 ) + $col_width * ( $j - 1 ) ) - $col_width / 2;
            my $y = $row_y - $row_height / 2;
            $temp{cal}[ $i - 1 ][ $j - 1 ] = {
                x      => $x,
                y      => $y,
                height => $row_height,
                width  => $col_width,
                text   => {
                    x      => $x + $col_margin_width * $TEXT_OFFSET_X,
                    y      => $y + $row_height * $TEXT_OFFSET_X,
                    length => $col_width * $TEXT_WIDTH_RATIO,
                    class  => 'mday ',
                    style  => 'font-size: ' . ( $row_height * $TEXT_HEIGHT_RATIO ),
                },
            };
        }
    }

    # set up the week day headings
    my $count = 1;
    for my $day (qw/Mon Tue Wed Thu Fri Sat Sun/) {
        my $x = $temp{bb}{x} + $col_margin_width * ( 2 * $count + 1 ) + $col_width * ( $count - 1 ) + $col_width / 2;
        my $y = $temp{bb}{y} + $row_margin_height;
        $temp{cal}[0][$count] = {
            x      => $x,
            y      => $y,
            height => $row_height * $self->{calendar_height},
            width  => $col_width,
            text   => {
                text   => $day,
                x      => $x + $col_width / $HEADING_DOW_WIDTH_SCALE,
                y      => $y + $row_height * $HEADING_DOW_HEIGHT_SCALE,
                length => $col_width * $HEADING_WIDTH_SCALE,
                adjust => 'spacing',                                #AndGlyphs',
                class  => 'day ' . lc $day,
                style  => 'font-size: ' . ( $row_height * $HEADING_HEIGHT_SCALE ),
            },
        };
        $count++;
    }

    # set up the week of the year column
    $count = 1;
    for my $week ( 1 .. $MAX_WEEK_ROW ) {
        my $x = $temp{bb}{x} + $col_margin_width;
        my $y = $temp{bb}{y} + $row_margin_height * ( 2 * $count + 1 ) + $row_height * ( $count - 1 ) + $row_height / 2;
        $temp{cal}[$count][0] = {
            x      => $x,
            y      => $y,
            height => $row_height,
            width  => $col_width / 2,
            text   => {
                text   => $week,
                x      => $x + $col_width / $HEADING_WOY_WIDTH_SCALE,
                y      => $y + $row_height * $HEADING_WOY_HEIGHT_SCALE,
                length => $col_width * $HEADING_WIDTH_SCALE,
                adjust => 'spacing',                                #AndGlyphs',
                class  => 'week',
                style  => 'font-size: ' . ( $row_height * $HEADING_HEIGHT_SCALE ),
            },
        };
        $count++;
    }

    # get the month display stuff
    $temp{month} = {
        x     => $temp{bb}{x} + $col_margin_width * 2,
        y     => $temp{bb}{y} - $ymargin/2,
        style => 'font-size: ' . ($row_height),
    };

    # set up the year display
    $temp{year} = {
        x     => $temp{bb}{x} + $temp{bb}{width},
        y     => $temp{bb}{y} - $ymargin/2,
        style => 'text-align: end; text-anchor: end; font-size: ' . $row_height,
    };

    $self->{template} = \%temp;

    return;
}

sub get_page {

    my $self = shift;
    my $page = ref $self->{page} ? $self->{page}{page} : $self->{page};
    my %size = ( width => '210.00mm', height => '297.00mm' );

    if ($page) {
        %size =
              $page eq 'A0' ? ( width => '840.00mm', height => '1188.00mm' )
            : $page eq 'A1' ? ( width => '594.00mm', height => '840.00mm' )
            : $page eq 'A2' ? ( width => '420.00mm', height => '594.00mm' )
            : $page eq 'A3' ? ( width => '297.00mm', height => '420.00mm' )
            : $page eq 'A4' ? ( width => '210.00mm', height => '297.00mm' )
            : $page eq 'A5' ? ( width => '148.50mm', height => '210.00mm' )
            : $page eq 'A6' ? ( width => '105.00mm', height => '148.50mm' )
            :                 croak "Unknown page type '$page'!\n";
    }

    if ( ref $self->{page} && $self->{page}{width} ) {
        $size{width} = $self->{page}{width};
    }
    if ( ref $self->{page} && $self->{page}{height} ) {
        $size{height} = $self->{page}{height};
    }

    # Get the values to internal variables
    my ( $width, $width_unit ) = $size{width} =~ /\A(.+?)(px|pt|mm|cm|m|in)?\Z/xms;
    $width *= 1.0;
    croak "Unable to get a width from $self->{page} or $self->{width}" if !$width;
    $width_unit ||= 'px';

    my ( $height, $height_unit ) = $size{height} =~ /\A(.+?)(px|pt|mm|cm|m|in)?\Z/xms;
    $height *= 1.0;
    croak "Unable to get a height from $self->{page} or $self->{height}" if !$height;
    $height_unit ||= 'px';

    # store the internal variables
    if ( !ref $self->{page} ) {
        $self->{page} = {};
    }
    $self->{page}{width}       = $width;
    $self->{page}{width_unit}  = $width_unit;
    $self->{page}{height}      = $height;
    $self->{page}{height_unit} = $height_unit;

    return (
        width       => $width,
        width_unit  => $width_unit,
        height      => $height,
        height_unit => $height_unit,
    );
}

sub output_year {

    my ( $self, @params ) = @_;
    my $file = pop @params;
    my ( $start, $end ) = @params;

    return if !$start;

    if ($end) {
        $start = strptime('%F', "$start-01");
        $end   = strptime('%F', "$end-01");
    }
    else {
        $start = strptime('%F', "$start-01-01");
        $end   = $start + $INTERVAL_ELEVEN_MONTHS;
    }

    my @files;
    while ( $start <= $end ) {
        my $month = $start->strftime('%Y-%m');
        push @files, "$file-$month.svg";
        $self->output_month( $month, "$file-$month.svg" );
        $start += $INTERVAL_ONE_MONTH;
    }

    return @files;
}

sub output_month {

    my ( $self, $month, $file, ) = @_;

    # add the month specific details to a clone of the general settings
    my $templ = clone $self->{template};
    my %size  = $self->get_page();
    $self->{full_moon} = 0;

    carp "Month '$month' is not the correct format (YYYY-MM) " if !$month || $month !~ /\A\d{4}-\d{2}\Z/xms;

    my $date = strptime('%F', "$month-01");
    $templ->{year}{text}  = $date->year();
    $templ->{month}{text} = $date->month_name();
    my $month_day = $date - $INTERVAL_ONE_WEEK;
    my $row       = 1;
    my $wrap      = 0;

    # make sure that we start on a monday
    while ( $month_day->wday() != 2 ) {
        $month_day += $INTERVAL_ONE_DAY;
    }

    DAY:
    for my $count ( 1 .. $MAX_DAYS ) {

        # get the day of the week (of the first day of the month)
        my $wday = $month_day->wday();
        $wday = $wday == 1 ? $ONE_WEEK : $wday - 1;
        my $r = $templ->{cal}[$row][$wday]{width} / $DAY_COLS;
        if ( $self->{moon}{radius} ) {
            $r *= $self->{moon}{radius};
        }

        $templ->{cal}[$row][$wday]{text}{text} = $month_day->mday();
        $templ->{cal}[$row][$wday]{current} = $date->month() == $month_day->month() ? 1 : 2;
        if ( $date->month() == $month_day->month() ) {
            $templ->{cal}[$row][$wday]{text}{class} .= 'current_month';
        }

        if ( $self->{moon} && $self->{moon}{display} ) {

            # get the phase info at 8:00pm
            my $moon_date = $month_day + $MOON_AT_NIGHT;
            my $phase     = $self->get_moon_phase($moon_date);
            $templ->{cal}[$row][$wday]{moon} = $self->moon(
                phase => $phase,
                id    => 'moon_' . $month_day->strftime('%Y-%m-%d'),
                x     => $templ->{cal}[$row][$wday]{x} + $r + $templ->{cal}[$row][$wday]{width} * $MOON_SCALE_WIDTH   + $self->{moon}{xoffset},
                y     => $templ->{cal}[$row][$wday]{y} - $r + $templ->{cal}[$row][$wday]{height} * $MOON_SCALE_HEIGHT + $self->{moon}{yoffset},
                r     => $r,
            );
        }

        if ( $wday == $ONE_WEEK ) {
            $row++;
        }
        if ( $row > $MAX_WEEK_ROW ) {
            $row  = 1;
            $wrap = 1;
        }
        $month_day += $INTERVAL_ONE_DAY;

        # stop if we leave the current month.
        last DAY if $wrap && $date->month() != $month_day->month();
    }

    # process the image if present
    if ( $self->{image} && ( $self->{image}{src} || $self->{image}{$month} ) ) {
        my $image = $self->{image}{$month} || $self->{image}{src};
        $templ->{image}{src} = $image;

        $templ->{image}{x} = $self->{page}{x_margin};
        $templ->{image}{y} = $self->{page}{y_margin};

        if ( -f $image ) {
            my $info = ImageInfo($image);
            if ( $info->{ImageHeight} && $info->{ImageWidth} ) {
                $templ->{image}{x}      = $self->{page}{x_margin};
                $templ->{image}{y}      = $self->{page}{y_margin};
                $templ->{image}{width}  = $self->{page}{width}  - 2 * $self->{page}{x_margin};
                $templ->{image}{height} = $self->{page}{height} * (1 - $self->{calendar_height}) - $self->{page}{y_margin} * 2;

                my $image_scale = $info->{ImageHeight}    / $info->{ImageWidth};
                my $page_scale  = $templ->{image}{height} / $templ->{image}{width};

                if ($image_scale < $page_scale) {
                    $templ->{image}{y}      -= ( $templ->{image}{height} - ( $templ->{image}{height} * $page_scale / $image_scale ) ) / 2;
                    $templ->{image}{height} *= $image_scale / $page_scale;
                }
                else {
                    $templ->{image}{x}     += ( $templ->{image}{width} - ( $templ->{image}{width} * $page_scale / $image_scale ) ) / 2;
                    $templ->{image}{width} *= $page_scale / $image_scale;
                }
            }
            else {
                die "The image $image doesn't apear to have a height or width\n";
            }
        }
    }

    return $self->output( $file, $templ );
}

sub output {

    my ( $self, $file, $template ) = @_;

    my $fh;
    my %option = ( EVAL_PERL => 1 );
    $option{INCLUDE_PATH} = $self->{INCLUDE_PATH} || dist_dir('SVG-Calendar');
    if ( $self->{path} ) {
        $option{INCLUDE_PATH} .= ':' . $self->{path};
    }

    my $tmpl = $self->{tt} || Template->new(%option);

    my $text;
    print Dumper($template) if $self->{verbose};

    $tmpl->process( 'calendar.svg', $template, \$text )
        or croak( $tmpl->error );

    if ($file) {
        if ( $file eq q/-/ ) {
            print $text or carp "Could not write to STDOUT: $OS_ERROR\n";
        }
        else {
            open $fh, q/>/, $file or croak "Cannot write SVG to file '$file': $!\n";

            print {$fh} $text or carp "Could not write to file '$file': $OS_ERROR\n";

            close $fh or carp "There was an issue closing file '$file': $OS_ERROR\n";

            if ( -f $file && $self->{inkscape} ) {
                if ( $self->{inkscape}{pdf} ) {
                    # get inkscape to convert svg to PDF
                }
                if ( $self->{inkscape}{print} ) {
                    # get inkscape to print out the document
                }
            }
        }
    }

    $self->{tt} = $tmpl;
    return $text;
}

sub moon {
    my ( $self, %params ) = @_;

    my $phase  = $params{phase};
    my $id     = $params{id};
    my $x      = $params{x} || $FULL_MOON;
    my $y      = $params{y} || $FULL_MOON;
    my $r      = $params{r} || $FULL_MOON;
    my $class  = q//;

    # approx error of less than one lunar day
    my $error = 2 * pi / 56;  ## no critic

    my $moon = { id => $id };

    # moon phases 0 == new moon 3 == last quarter

    my ( $sx, $sy ) = ( $x, $y );
    my ( $ex, $ey ) = ( $x, $y + 2 * $r );

    if ( $phase < $error || 2 * pi - $error < $phase ) {
        $class = ' new-moon';
    }
    elsif ( pi - $error < $phase && $phase < pi + $error ) {

        # approx full moon
        my $moon_type = $self->{full_moon}++ ? 'blue-moon' : 'full-moon';
        $moon->{highlight} = {
            type  => 'circle',
            id    => $id,
            class => $moon_type,
            cx    => $x,
            cy    => ( $sy + $ey ) / 2,
            r     => $r,
        };
    }
    elsif ( $phase < pi ) {

        # moon waxing partial
        my $d = "M $sx\t$sy C ";
        $d .= ( $sx + $r * $MOON_RADIAL_STEP ) . q/ / . $sy . q/,/;
        $d .= ( $sx + $r * $MOON_RADIAL_STEP ) . q/ / . $ey;
        $d .= ",$ex\t$ey C ";
        $d .= ( $ex - $r * $MOON_RADIAL_STEP * ( -cos($phase) ) ) . q/ / . ( $ey + $r / 2 * ( -sin($phase) ) ) . q/,/;
        $d .= ( $ex - $r * $MOON_RADIAL_STEP * ( -cos($phase) ) ) . q/ / . ( $sy - $r / 2 * ( -sin($phase) ) );
        $d .= ", $sx\t$sy Z";
        $moon->{highlight} = {
            type  => 'path',
            id    => $id,
            d     => $d,
        };
    }
    elsif ( $phase > pi ) {

        # moon waning partial
        my $d = "M $sx\t$sy C ";
        $d .= ( $sx - $r * $MOON_RADIAL_STEP ) . q/ / . $sy . q/,/;
        $d .= ( $sx - $r * $MOON_RADIAL_STEP ) . q/ / . $ey;
        $d .= ",$ex\t$ey C ";
        $d .= ( $ex + $r * $MOON_RADIAL_STEP * ( -cos($phase) ) ) . q/ / . ( $ey - $r / 2 * ( -sin($phase) ) ) . q/,/;
        $d .= ( $ex + $r * $MOON_RADIAL_STEP * ( -cos($phase) ) ) . q/ / . ( $sy + $r / 2 * ( -sin($phase) ) );
        $d .= ", $sx\t$sy Z";
        $moon->{highlight} = {
            type  => 'path',
            id    => $id,
            d     => $d,
        };
    }

    $moon->{border} = {
        id    => "moon_border_$id",
        class => "outline$class",
        cx    => $x,
        cy    => ( $sy + $ey ) / 2,
        r     => $r,
    };

    return $moon;
}

sub get_moon_phase {

    my ( $self, $date ) = @_;

    if ( !blessed $date || !$date->isa('DateTime') ) {
        $date = strptime('%F %T', "$date 20:00:00");
    }

    if ( !$date ) {
        carp 'Unable to create a date!';
    }

    # check if we have a way to calculate the phase of the moon
    if ( !$self->{moon_phase} ) {
        my @packages = qw/Astro::Coord::ECI::Moon Astro::MoonPhase/;

        PACKAGE:
        for my $package (@packages) {
            my $package_file = $package;
            $package_file =~ s{::}{/}gxms;

            eval{ require $package_file . '.pm' };  ## no critic
            if ( !$EVAL_ERROR ) {
                $self->{moon_phase} = $package;
                last PACKAGE;
            }
        }

        # croak if there is no way to calculate the phase of the moon
        if ( !$self->{moon_phase} ) {
            die "Cannot find any packages installed to calculate the moon phase\nTry installing one of:\ncpan "
                . join( "\ncpan ", @packages ) . "\n";
        }
    }

    my $phase;
    if ( $self->{moon_phase} eq 'Astro::Coord::ECI::Moon' ) {

        # phase in radians
        $phase = Astro::Coord::ECI::Moon->phase( $date->epoch() );
    }
    elsif ( $self->{moon_phase} eq 'Astro::MoonPhase' ) {

        # phase in fraction of circle
        ($phase) = Astro::MoonPhase::phase( $date->epoch() );
        $phase *= 2 * pi;
    }

    return $phase;
}

1;

__DATA__

=head1 NAME

SVG::Calendar - Creates calendars in SVG format which can be printed

=head1 VERSION

This documentation refers to SVG::Calendar version 0.3.12.

=head1 SYNOPSIS

   use SVG::Calendar;

   # Brief but working code example(s) here showing the most common usage(s)
   # This section will be as far as many users bother reading, so make it as
   # educational and exemplary as possible.

   # Create a new (basic) SVG::Calendar object for producing A4 calendars
   my $svg = SVG::Calendar->new( page => 'A4' );

   # print to standard out the calendar for June 2006
   print $svg->output_month( '2006-06' );

   # create a calendar for the year 2007 with filenames
   #   my-calendar-2015-01.svg
   #   ...
   #   my-calendar-2015-12.svg
   $svg->output_year( '2007', 'my-calendar' );

=head1 DESCRIPTION

This module generates an SVG image for one or more months for a calendar.

=head1 SUBROUTINES/METHODS

=head3 C<new ( %args )>

Arg: C<page> - hash ref - description

Arg: C<moon> - hash ref - description

Arg: C<image> - hash ref - description

Arg: C<path> - string - Directory containing alternate svg template version

Arg: C<inkscape> - hash ref - Use inkscape to convert the SVG to a PDF or to
print out the generated SVG calendar.

Return: SVG::Calendar - A new SVG::Calendar object

Description: Creates and sets up a new SVG::Calendar object

=head3 C<init ( )>

Initialises the calendar object

=head3 C<get_page ( )>

Return: hash - contains the page height and width and the units used

Description: Gets the dimensions of the page based on the parameters
supplied at creation time

=head3 C<output_year ( ($start, $end | $year), $file  )>

Param: C<$start> - string ('YYYY-MM') - description

Param: C<$end> - string ('YYYY-MM') - description

Param: C<$year> - int (year) - description

Param: C<$file> - string - The base name for the SVG files calendars for each
year

Return: list - A list of the files created

Description: Creates the SVG calendar files for each month of the year (or for
each month from start and end)

 eg $svg->output_year( 2006, 'folowers' );

 Will result in the following files created

 flowers-2006-01.svg
 flowers-2006-02.svg
 ..
 flowers-2006-11.svg
 flowers-2006-12.svg

=head3 C<output_month ( $month, $file,  )>

Param: C<$month> - string (detail) - The month that the calendar page should
display (format YYYY-MM)

Param: C<$file> - string (detail) - The file to save the output to if defined.
if $file eq '-' prints to STDOUT

Return: string - The SVG text to display the calendar page

Description: Outputs a particular months calendar...

(Adds the week of the year and the

=head3 C<output ( $file )>

Param: C<$file> - string (detail) - The file name to print the SVG file to (if undefined it will print nothing)

Return: scalar - The SVG text.

Description:

  <path
     style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.25000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
     d="M 264.88031,225.97672 C 518.24408,341.14207 267.18361,490.85702 267.18361,490.85702 L 264.88031,225.97672 z "
     id="path1460"
     sodipodi:nodetypes="ccc" />
  <path
     style="fill:none;fill-opacity:0.75000000;fill-rule:evenodd;stroke:#000000;stroke-width:0.25000000pt;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1.0000000"
     d="M 628.80282,189.12380 C 854.52691,299.68254 847.54045,393.30582 626.49951,477.03718 C 579.56639,494.81567 769.30455,334.23215 628.80282,189.12380 z "
     id="path1464"
     sodipodi:nodetypes="csc" />
  <path
     style="fill: green; fill-opacity: 0.25; stroke: black;"
     d="M 0 0 C 133.3333 8, 133.3333 192, 0 200 C -133.333 192 -133.3333 8 Z"
        M 0 0 C 133.3333 8  133.3333 192, 0 200 C -133.333 192 -133.3333 8 Z
     id="test" />
  <circle
     style="fill: none; stroke: red; stroke-opacity: 0.5"
     cx="0"
     cy="100"
     r="100"
     id="circle" />

=head3 C<moon ( %params )>

Param: C<phase> - float - 0 <= $phase < 2 * pi, represents the phase of the moon

Param: C<id> - string - The id that the moon SVG part should use

Param: C<x> - float - The X coordinate of the left hand side of the moon to be drawn

Param: C<y> - float - The Y coordinate of the top side of the moon to be drawn

Param: C<r> - float - The Radius of the the moon to be drawn

Return: SVG part - The SVG to display the moon in the phase passed in

Description: From the phase information this function calculates the details
of the curve to represent the phase of the moon and puts it on the diagram
based on the x, y and r parameters.

=head3 C<get_moon_phase ( $date )>

Param: C<$date> - date (DateTime object or string to convert to one) - The
date that the moon phase is desired

Return: float - The phase of the moon from 0 (new moon) via 2 (full moon) to
< 4 (next new moon)

Description: This method calculates the phase of the moon (it will what ever
it can find to calculate the phase)

=head1 DIAGNOSTICS

=head1 CONFIGURATION AND ENVIRONMENT

=head1 DEPENDENCIES

=head1 INCOMPATIBILITIES

=head1 BUGS AND LIMITATIONS

There are no known bugs in this module.

Please report problems to Ivan Wills (ivan.wills@gmail.com).

Patches are welcome.

=head1 AUTHOR

Ivan Wills - (ivan.wills@gmail.com)
<Author name(s)>  (<contact address>)

=head1 LICENSE AND COPYRIGHT

Copyright (c) 2006-2009 Ivan Wills (14 Mullion Close, Hornsby Heights, NSW Australia 2077)
All rights reserved.


This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself. See L<perlartistic>.  This program 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.

=cut