### ----------------------------------------------------
### -- PDF::Document
### -- Licenses: Artistic-2.0
### -- Authors: Tom Browder
### -- File: lib/PDF/Document.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
        $angle = self!value2radians($angle) if $angle.defined;
        my $cx = self!value2points: $x;
        my $cy = self!value2points: $y;
        my $ca = self!value2points: $a;
        my $cb = self!value2points: $b;
        self!draw-ellipse($cx, $cy, $ca, $cb, :$fill, :$linewidth, :$color);
    }
    method !draw-ellipse($x, $y, $a, $b,
        Real :$angle, # radians
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        self.Save;
        if $angle.defined {
            self.page.gfx.transform: :rotate($angle);
        }
        self.setlinewidth: $linewidth, :$color;

        note "DEBUG: draw-ellipse: x = $x, y = $y, a = $a, b = $b" if self.debug;
        # from stack overflow: copyright 2022 by Spencer Mortenson
        # treat $a as length in x direction, $b as length in y direction
        constant c = 0.551915024495;
        if 0 {
            # TODO use .transform: :scale[$a,$b]
            my $tx = $x;
            my $ty = $y;
            self.page.gfx.transform: :translate[$tx, $ty];
            self.page.gfx.transform: :scale[$a, $b];
            self.MoveTo: 1, 0;
            # use four curves (x/y)
            self.CurveTo:  c, 1,  1, c,  1, 0;
            self.CurveTo:  1,-c,  c,-1,  0,-1;
            self.CurveTo: -c,-1, -1,-c, -1, 0;
            self.CurveTo: -1, c, -c, 1,  0, 1;
            self.ClosePath;
            if $fill { self.Fill; }
            else { self.Stroke; }
        }
        else {
            # Draw the elipse starting at the top and working
            # clockwise.
            self.page.gfx.transform: :translate[$x, $y];
            self.MoveTo: 0*$a, 1*$b;
            # use four curves (x/y)
            self.CurveTo:  c*$a, 1*$b,  1*$a, c*$b,  1*$a, 0*$b;
            self.CurveTo:  1*$a,-c*$b,  c*$a,-1*$b,  0*$a,-1*$b;
            self.CurveTo: -c*$a,-1*$b, -1*$a,-c*$b, -1*$a, 0*$b;
            self.CurveTo: -1*$a, c*$b, -c*$a, 1*$b,  0*$a, 1*$b;
            self.ClosePath;
            if $fill { self.Fill; }
            else { self.Stroke; }
        }
        self.Restore;
    }

    # This is the method that the other rectangle methods should resolve to
    # as it actually renders the figure.
    method !draw-rectangle(Numeric $llx, Numeric $lly, Numeric $urx, Numeric $ury,
        :$angle, # radians
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0,
        ) {
        self.Save;
        if $angle.defined {
            self.page.gfx.transform: :rotate($angle);
        }
        self.setlinewidth: $linewidth, :$color;
        self.MoveTo: $llx, $lly;
        self.LineTo: $urx, $lly;
        self.LineTo: $urx, $ury;
        self.LineTo: $llx, $ury;
        self.ClosePath;
        if $fill { self.Fill; }
        else { self.Stroke; }
        self.Restore;
    }
    multi method rectangle($llx is copy, $lly is copy, $urx is copy, $ury is copy,
        :$angle is copy,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $angle = self!value2radians($angle) if $angle.defined;
        # from upper-left corner
        $llx = self!value2points: $llx;
        $lly = self!value2points: $lly;
        $urx = self!value2points: $urx;
        $ury = self!value2points: $ury;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$angle, :$color;
    }

    multi method rectangle(:$llx! is copy, :$ury! is copy, :$width!, :$height!,
        :$angle is copy,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $angle = self!value2radians($angle) if $angle.defined;
        # from upper-left corner
        $llx = self!value2points: $llx;
        $ury = self!value2points: $ury;
        my $w = self!value2points: $width;
        my $h = self!value2points: $height;
        my $urx = $llx + $w;
        my $lly = $ury - $h;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$angle, :$color;
    }
    multi method rectangle(:$urx! is copy, :$ury! is copy, :$width!, :$height!,
        :$angle is copy,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $angle = self!value2radians($angle) if $angle.defined;
        # from upper-right corner
        $urx = self!value2points: $urx;
        $ury = self!value2points: $ury;
        my $w = self!value2points: $width;
        my $h = self!value2points: $height;
        my $llx = $urx - $w;
        my $lly = $ury - $h;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$angle, :$color;
    }
    multi method rectangle(:$urx! is copy, :$lly! is copy, :$width!, :$height!,
        :$angle is copy,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $angle = self!value2radians($angle) if $angle.defined;
        # from lower-right corner
        $urx = self!value2points: $urx;
        $lly = self!value2points: $lly;
        my $w = self!value2points: $width;
        my $h = self!value2points: $height;
        my $llx = $urx - $w;
        my $ury = $lly + $h;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$angle, :$color;
    }
    multi method rectangle(:$cx! is copy, :$cy! is copy, :$width!, :$height!,
        :$angle is copy,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $angle = self!value2radians($angle) if $angle.defined;
        # from the center
        $cx = self!value2points: $cx;
        $cy = self!value2points: $cy;
        my $hw = 0.5 * self!value2points: $width;
        my $hh = 0.5 * self!value2points: $height;

        my $llx = $cx - $hw;
        my $lly = $cy - $hw;
        my $urx = $cx + $hh;
        my $ury = $cy + $hh;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$angle, :$color;
    }

    method wedge(
        # TODO finish
        :$cx! is copy, :$cy! is copy, :$radius!,
        :$start-angle!, :$end-angle!,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
    }

    method circular-arc(
        # TODO finish
        :$cx! is copy, :$cy! is copy, :$radius!,
        :$start-angle!, :$end-angle!,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $cx    = self!value2points: $cx;
        $cy    = self!value2points: $cy;
        my $r  = self!value2points: $radius;
        my $sa = self!value2radians: $start-angle;
        my $ea = self!value2radians: $end-angle;
        # Scheme is to create the circle, then
        # create a triangle to cut out the
        # perimeter between the desired angles.
        self.Save;
        # translate to the center
        # rotate
        # create the circle
        # create the cutting wedge with radius 2r
        self.wedge: :cx(0), :cy(0), :radius(2*$r), :start-angle($sa), :end-angle($ea), :fill(True);
        self.Restore;
    }

    method elliptical-arc(
        # TODO finish
        :$cx! is copy, :$cy! is copy, :$radius!,
        :$a! is copy, :$b! is copy,
        :$start-angle!, :$end-angle!,
        :$rot-angle = 0,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        $cx = self!value2points: $cx;
        $cy = self!value2points: $cy;
        $a  = self!value2points: $b;
        $b  = self!value2points: $b;
        my $sa = self!value2radians: $start-angle;
        my $ea = self!value2radians: $end-angle;
        my $ra = self!value2radians: $rot-angle;
        # Scheme is to create the ellipse, then
        # create a triangle to cut out the
        # perimeter between the desired angles.
    }

    multi method rectangle(:$llx! is copy, :$lly! is copy, :$width!, :$height!,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        # from lower-left corner
        $llx = self!value2points: $llx;
        $lly = self!value2points: $lly;
        my $w = self!value2points: $width;
        my $h = self!value2points: $height;
        my $urx = $llx + $w;
        my $ury = $lly + $h;
        self!draw-rectangle: $llx, $lly, $urx, $ury, :$fill, :$linewidth, :$color;
    }

    multi method circle(:$x!, :$y!, :$radius!,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        my $cx = self!value2points: $x;
        my $cy = self!value2points: $y;
        my $cr = self!value2points: $radius;
        self!draw-circle($cx, $cy, $cr, :$fill, :$linewidth, :$color);
    }
    method !draw-circle($x, $y, $r,
        :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        self.Save;
        self.setlinewidth: $linewidth, :$color;
        # from stack overflow: copyright 2022 by Spencer Mortenson
        self.page.gfx.transform: :translate[$x, $y];
        constant c = 0.551915024495;
        self.MoveTo: 0*$r, 1*$r; # top of the circle
        # use four curves
        self.CurveTo:  c*$r, 1*$r,  1*$r, c*$r,  1*$r, 0*$r;
        self.CurveTo:  1*$r,-c*$r,  c*$r,-1*$r,  0*$r,-1*$r;
        self.CurveTo: -c*$r,-1*$r, -1*$r,-c*$r, -1*$r, 0*$r;
        self.CurveTo: -1*$r, c*$r, -c*$r, 1*$r,  0*$r, 1*$r;
        self.ClosePath;
        if $fill { self.Fill; }
        else { self.Stroke; }
        self.Restore;
    }

    method polyline(@pts, :$fill = False, :$closepath = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        # array of x/y points, path is not closed
        my $np = @pts.elems;
        die "FATAL: polyline points array is empty" if not $np;
        die "FATAL: polyline points array ($np pts) must have an even number of entries" if $np mod 2;
        my $x = @pts.shift;
        my $y = @pts.shift;
        self.Save;
        self.SetLineWidth: $linewidth;
        self.setcolor: $color;
        self.MoveTo: $x, $y;
        while @pts.elems {
            $x = @pts.shift;
            $y = @pts.shift;
            self.LineTo: $x, $y;
        }
        self.ClosePath if $closepath;
        if $closepath and $fill { self.Fill; }
        else { self.Stroke; }
        self.Restore;
    }
    method polygon(@pts, :$fill = False,
        :$color = [0], # black
        :$linewidth = 0
        ) {
        # array of x/y points, path is closed
        my $np = @pts.elems;
        die "FATAL: polygon points array is empty" if not @pts.elems;
        die "FATAL: polygon points array ($np pts) must have an even number of entries" if $np mod 2;
        self.polyline: @pts, :$fill, :closepath(True), :$linewidth, :$color;
    }

    method !moon-waxing(
        # waxing, New Moon to Full Moon, light increasing from the right (frac 0..1)
        Real $cx,
        Real $cy,
        Real $radius where {$_ >= 0},
        Real $frac where {0 <= $_ <= 1},
        :$angle,
        :$hemi where {/:i n|s/} = 'n',
        ) {

        self.Save;
        self.page.gfx.transform: :translate[$cx,$cy];

        if $hemi.defined and $hemi ~~ /:i s/ {
            self.page.gfx.transform: :reflect(pi/2);
        }
        if $angle.defined {
            self.page.gfx.transform: :rotate($angle);
        }

        if $frac < 0.5 {
            # New Moon to First Quarter
            # 1. left semicircle is black
            #    make black-filled circle
            self!draw-circle: 0, 0, $radius, :fill(True);
            #    make white square covering right semicircle
            self.rectangle: :cx($radius), :cy(0), :width(2*$radius), :height(2*$radius), :fill(True), :color(1);
            # 2. black on right semicircle
            #    when frac = 0.0, a = radius
            #    when frac = 0.5, a = 0
            #    make black-filled ellipse with a = radius - (2 * radius * frac)
            my $dfa = 2 * $radius * $frac;
            self!draw-ellipse: 0, 0, $radius - $dfa, $radius, :fill(True);
        }
        elsif $frac >= 0.5 {
            # First Quarter to Full Moon
            # 1. right semicircle is white
            #    make black circle
            self!draw-circle: 0, 0, $radius, :fill(True);
            #    make white-filled square covering right semicircle
            self.rectangle: :cx($radius), :cy(0), :width(2*$radius), :height(2*$radius), :fill(True), :color(1);
            # 2. white on left semicircle
            #    when frac = 1.0, a = radius
            #    when frac = 0.5, a = 0
            #    make white-filled ellipse with a = (2 * radius * frac) - radius
            my $dfa = 2 * $radius * $frac;
            self!draw-ellipse: 0, 0, $dfa - $radius, $radius, :fill(True), :color(1);
        }
        # 3. stroke the circle's circumference
        self!draw-circle: 0, 0, $radius;
        self.Restore;
    }

    method !moon-waning(
        # waning, Full Moon to New moon, darkness increasing from the
        # right (frac 1..0)
        Real $cx,
        Real $cy,
        Real $radius where {$_ >= 0},
        Real $frac where {0 <= $_ <= 1},
        :$angle,
        :$hemi where {/:i n|s/ } = 'n',
        ) {
        self.Save;
        self.page.gfx.transform: :translate[$cx,$cy];

        if $hemi.defined and $hemi ~~ /:i s/ {
            self.page.gfx.transform: :reflect(pi/2);
        }
        if $angle.defined {
            self.page.gfx.transform: :rotate($angle);
        }

        if $frac >= 0.5 {
            # Full Moon to Third Quarter
            # 1. right semicircle is white to begin with
            #    make black circle
            self!draw-circle: 0, 0, $radius, :fill(True);
            #    make white-filled square covering left semicircle
            self.rectangle: :cx(-$radius), :cy(0), :width(2*$radius), :height(2*$radius), :fill(True), :color(1);
            # 2. white on right semicircle is frac - 0.5
            #    make white-filled ellipse with a = (2 * radius * frac) - radius
            my $dfa = 2 * $radius * $frac;
            self!draw-ellipse: 0, 0, $dfa - $radius, $radius, :fill(True), :color(1);
        }
        elsif $frac < 0.5 {
            # Third Quarter to New Moon
            # 1. left semicircle is white to begin with
            #    make black-filled circle
            self!draw-circle: 0, 0, $radius, :fill(True);
            #    make white square covering left semicircle
            self.rectangle: :cx(-$radius), :cy(0), :width(2*$radius), :height(2*$radius),
                            :fill(True), :color(1);
            # 2. black on left semicircle is 0.5 - frac
            #    make black-filled ellipse with a = radius - (2 * radius * frac)
            my $dfa = 2 * $radius * $frac;
            self!draw-ellipse: 0, 0, $radius - $dfa, $radius, :fill(True);
        }
        # 3. stroke the circle's circumference
        self!draw-circle: 0, 0, $radius;
        self.Restore;
    }

    method moon-phase(
        :$cx! is copy,
        :$cy! is copy,
        :$radius! is copy,
        :$frac! where {0 <= $_ <= 1},
        :$type! where {/:i wax|wan/},
        :$hemi where {/:i n|s/} = 'n',
        :$angle is copy,
        ) {
        # Until we get circular and elliptical arcs we will have
        # to use circles and ellipses and overlay black with
        # white for certain input combinations.
        $cx     = self!value2points: $cx;
        $cy     = self!value2points: $cy;
        $radius = self!value2points: $radius;
        $angle  = self!value2radians($angle) if $angle.defined;

        if $type.contains('wax') {
            # Waxing: New Moon to Full Moon, light increasing from the right (frac 0..1)
            # (from the left in the Southern Hemisphere).
            self!moon-waxing: $cx, $cy, $radius, $frac, :$angle, :$hemi;
        }
        else {
            # Waning, Full Moon to New moon, darkness increasing from the right (frac 1..0)
            # (from the left in the Southern Hemisphere).
            self!moon-waning: $cx, $cy, $radius, $frac, :$angle, :$hemi;
        }
    }

    method get-gfx-state(:$delta) {
        if $delta.defined {
            return self.page.gfx.graphics-state(:$delta);
        }
        else {
            return self.page.gfx.graphics-state;
        }
    }

    method get-content() {
        return self.page.gfx.content-dump;
    }

    # Many other methods are provided by roles "PDF-role"
    # and "AFM-role".
    # Note those roles are auto-generated by program
    # dev/generate-code.raku.

    method show-media(:$names) {
        # see all the available media keys
        my %e = PageSizes.enums;

        if $names {
            # just return the names
            return %e.keys.sort
        }

        # fancier, more info
        say "Media names and sizes (in PS points)";
        say "  Orientation: portrait";
        say "  Size => [width height]";
        say();
        say "                   Points       Inches";
        my $nc = 0;
        my ($n2, $n3) = 0, 0;
        my ($n2i, $n3i) = 0, 0;
        for %e.keys -> $k {
            my $v = %e{$k};
            my $n = $k.chars;
            $nc = $n if $nc < $n;

            # also get size of width and height
            my @n = $v.words;
            my $nw = @n[2].chars;
            my $nl = @n[3].chars;
            $n2 = $nw if $n2 < $nw;
            $n3 = $nl if $n3 < $nl;

            # and in inches
            my $wi = @n[2] / 72.0;
            my $li = @n[3] / 72.0;
            my $wis = sprintf '%5.2f', $wi;
            my $lis = sprintf '%5.2f', $li;
            my $nwi = $wis.chars;
            my $nli = $lis.chars;
            $n2i = $nwi if $n2i < $nwi;
            $n3i = $nli if $n3i < $nli;
        }
        for %e.keys.sort -> $k {
            my $v = %e{$k};
            my $nam = sprintf '%-*.*s', $nc, $nc, $k;

            my @n = $v.words;
            my $w = @n[2];
            my $l = @n[3];
            my $val = sprintf '%*.*s %*.*s',
                          $n2, $n2, $w,
                          $n3, $n3, $l;

            my $wi = @n[2] / 72.0;
            my $li = @n[3] / 72.0;
            my $wis = sprintf '%5.2f', $wi;
            my $lis = sprintf '%5.2f', $li;
            my $val2 = sprintf '%*.*s %*.*s',
                          $n2i, $n2i, $wis,
                          $n3i, $n3i, $lis;

            say "    $nam => [$val] [$val2]";
        }
    }
} # end of class Doc


sub make-page(Doc :$doc!,
              :$media     = <Letter>, # default
              :$landscape = False,
              :$debug,
             ) is export {
    my $page = $doc.pdf.page;
    # always save the CTM
    $doc.save;

    #set-media-box :$page, :$media;
    note $page.media-box if $debug;

    my $orient = "Portrait";
    my ($llx, $lly, $urx, $ury) = $page.media-box;
    note "media-box before translation: $llx, $lly, $urx, $ury" if $debug;
    my $pw = $urx - $llx;
    if $landscape {
        # just rotate the page
        ($llx, $lly, $urx, $ury) = to-landscape $page.media-box;
        $orient = "Landscape";
        $doc.translate: $pw, 0;
        $doc.rotate(90 * deg2rad);
        note "media-box after translation: $llx, $lly, $urx, $ury" if $debug;
    }
    $pw = $urx - $llx;

    my $cx = 0.5 * ($urx-$llx);
    my $cy = 0.5 * ($ury-$lly);

    # outline the page
    # method !rectangle(Real $llx, Real $lly, Real $urx, Real $ury,

    $doc.rectangle: 72, 72, $urx-72, $ury-72;
    # text at the center
    $doc.print: $orient, :x($cx), :y($cy), :align<center>, :valign<center>;

    # always restore the CTM
    $doc.restore;
}


