#!/usr/bin/env raku

use PDF::API6;
use PDF::Page;
use PDF::Content::Page :PageSizes;
use PDF::Content::Font::CoreFont;
constant CoreFont = PDF::Content::Font::CoreFont;

use Text::Utils :ALL;
use Config::TOML;

my %config; # toml hash
my $cfil = "{%*ENV<HOME>}/.pdfwriter/default.toml";

my $usage = 0;
if not @*ARGS.elems {
    say qq:to/HERE/;
    Usage: {$*PROGRAM.IO.basename} mode [options...] <input text file>

    Modes
      text - Converts the input file's lines to PDF (default)
      pod  - Converts the Raku pod file to PDF using :config
               options. See pod examples in the examples directory.

    Options
      number          - Add line numbers in the left column
      underline       - Stroke each baseline with a thin line
      title[=T]       - Add title to the page number line (default: input file name)
      size            - Font size (default: 10)
      font            - Font name (default: Courier)
      margins=l,r,t,b - Margins (default: one-inch on all sides)
      paper=T         - Where T is Letter (default), Legal, A4, or A5
      truncate=N      - Where N is the max number of chars on a line
                          and the rest are ignored
      wrap=N          - Where N is the max number of chars on a line
                          and the rest are wrapped to as many lines
                          as needed
      config=F        - Where F is the name of a TOML file other than
                          the default location
      verbose         - See more etails of the execution
      debug           - For developer use

    Notes
      Modes and options may be selected by the minimum number of unique
        leading characters, e.g., 'te' selects 'text' and 'tr' selects
        'truncate'.
      You may enter personal defaults in a TOML file defined at:
        '\$HOME/.pdfwriter/default.toml'
    HERE
    exit
}

my $ifil = @*ARGS.pop;
if not ($ifil.IO.f and $ifil.IO.r) {
    die "FATAL: Input file '$ifil' cannot be read.";
}

#enum Paper <Letter Legal A4 A5>; # this enum is already defined in PDF::API6
enum Font <Courier Times Helvetica>;
enum Weight <Bold>;
enum Style <Regular Italic Slant>;

my $text      = 1;
my $pod       = 0;
my $debug     = 0;
my $verbose   = 0;
my $toml;

class Doc {...}
my $doc = Doc.new;

class Doc {
    # doc attrs that depend upon PDF::API6
    has $.Page;
    has $.Font:

    # all attrs below here may be defined in the TOML file
    # doc attrs with defaults:
    has $.font  is rw = Courier;
    has $.size  is rw = 9.5;
    has $.paper is rw = Letter;
    has $.title is rw = 1;
    # margins
    has $.left   is rw = 72; # PS points (1 inch)
    has $.right  is rw = 72; # PS points (1 inch)
    has $.top    is rw = 72; # PS points (1 inch)
    has $.bottom is rw = 72; # PS points (1 inch)

    # the following doc attrs have false defaults (i.e., not defined):
    has $.number        is rw;
    has $.underline     is rw;
    has $.style         is rw;
    has $.weight        is rw;
    has $.truncate      is rw;
    has $.wrap          is rw;
    has $.leading       is rw = 9.5 * 1.25; # space between baselines
    has $.leading-ratio is rw = 1.25;       # used as font size times ratio equals leading distance

    # want some special methods
    method set-leading() {
        $!leading = $!size * $!leading-ratio;
    }
    multi method set-size($val, :$leading-ratio) {
        $!size          = $val;
        $!leading-ratio = $leading-ratio;
        $!leading       = $!size * $!leading-ratio;
    }
    multi method set-size($val, :$leading) {
        $!size          = $val;
        $!leading       = $leading;
        $!leading-ratio = $!leading / $!size;
    }
}


for @*ARGS {
    # options
    # doc options (may be defined in the config file)
    when $_.contains(/:i ^u/) { $doc.underline = 1 }
    when $_.contains(/:i ^n/) { $doc.number = 1 }
    when /:i tr <[uncate]>* '=' (\d+) / {
        $doc.truncate = +$0;
    }
    when /:i w <[rap]>* '=' (\d+) / {
        $doc.wrap = +$0;
    }
    when /:i pa <[per]>* '=' (\S+) / {
        $doc.paper = ~$0;
    }
    when /:i st <[tyle]>* '=' (\S+) / {
        $doc.style = ~$0;
    }
    when /:i si <[ze]>* '=' (\S+) / {
        $doc.size = ~$0;
    }
    when /:i w <[eight]>* '=' (\S+) / {
        $doc.weight = ~$0;
    }
    when /:i ti <[tle]>* ['=' (\S+)]? / {
        if $0.defined {
            $doc.title = ~$0;
        }
        else {
            $doc.title = $ifil;
        }
    }
    when /:i m <[argins]>* '=' (\S+) ',' (\S+) ',' (\S+) ',' (\S+) / {
        $doc.left   = +$0;
        $doc.right  = +$1;
        $doc.top    = +$2;
        $doc.bottom = +$3;
    }

    # other options
    when $_.contains(/:i ^v/) { $verbose = 1 }
    when $_.contains(/:i ^d/) { $debug = 1 }
    when /:i c <[onfig]>* '=' (\S+) / {
        $toml = ~$0;
    }

    # modes
    when $_.contains(/:i ^t/) { $text = 1; $pod = 0 }
    when $_.contains(/:i ^p/) { $pod = 1; $text = 0 }

    default { die "FATAL: Unknown arg '$_'" }
}

if $cfil.IO.r {
    %config = from-toml :file($cfil);
    note "Found your 'default.toml' file." if $debug;
    if $debug {
        note "DEBUG: Dumping \%config:";
        note %config.raku;
        note "DEBUG early exit";
        exit;
    }
    # extract and set vars
    my $err = 0;
    my $def = %config<default> // ++$err;
    if $err {
        note "WARNING: Cannot find config key: default.";
        note "         You may have unexpected results.";
        exit;
    }
    my %h = %config{$def};
    for %h.kv -> $k, $v {
        note "  $k => '$v'" if $debug;
        given $k {
            when $k eq 'underline' { $doc.underline = $k }
            default { ++$err; note "Unknown config key: $k" }
        }
    }
    note "WARNING: One or more unknown config keys were found.";
    note "         You may have unexpected results.";
}
else {
    note "No 'default.toml' file was found in your home directory, subdir '.pdfwriter'."
}

if $pod {
   die "FATAL: The pod mode is not yet implemented.";
}

# some default settings to get started
# TODO define a page class to hold such things
$doc.height = 11 * 72;
$doc.width = 8.5 * 72;

$doc.pdf = PDF::API6.new;
if $ifil ~~ /:i pdf $/ {
    die "FATAL: Not attempting to read a file with name ending in 'pdf': $ifil ";
}

my $ofil = $ifil ~ '.pdf';
if $debug {
   say "DEBUG: infile '$ifil'; outfile '$ofil'";
   exit;
}

# GLOBAL VALUES =====================================
# Set the default page size for all pages
$doc.pdf.media-box = $doc.paper;

# Use a standard PDF core font
$doc.Font = $doc.pdf.core-font: :font($doc.font); #, :weight<Bold>;
# default size

my @lines = $ifil.IO.lines;

text2pdf @lines, :$doc, :$debug;

# Save the new PDF
$doc.pdf.save-as($ofil);
say "See file '$ofil'";

##### subroutines #####
sub text2pdf(@lines, :$doc, :$debug) {
    # line-by-line (the original method

    # for now we just need a conversion to pdf
    # determine how many pages:
    my $lines-per-page = (($doc.height - ($doc.top - $doc.bottom)) / $doc.leading).floor;
    my $npages = (@lines.elems / $lines-per-page).ceiling;

    # Add a blank page to start
    my $page = $doc.pdf.add-page();
    my $x  = $doc.left;
    my $y0 = $doc.height - $doc.top - $doc.leading;
    my $y  = $y0;
    my $pnum      = 1; # for page numbering and control
    my $pnumbered = 0; # for page numbering and control
    for @lines -> $line {
        # add the line's text to the page
        $page.text: {
            .font = $doc.Font, $doc.size;
            .text-position = $x, $y;
            .say($line);
        }
        if $doc.underline {
            my $xx = $doc.width - $doc.right;
            $page.graphics: {
                # automatically protects with a Save/Restore
                # need thinnest line from $x to $width-$right
                .LineWidth = 0; # thin as possible
                .MoveTo($x, $y-1);
                .LineTo($xx, $y-1);
                .Stroke;
            }
        }

        $y -= $doc.leading;
        if $y <= $doc.bottom {
            note "DEBUG: numbering page $pnum of $npages" if $debug;
            # add a page number
            ++$pnumbered;
            my $pp;
            if $doc.title {
                $pp = "Page $pnum of $npages ($doc.title)";
            }
            else {
                $pp = "Page $pnum of $npages";
            }
            my $yy = 36; # 1/2 inches from the bottom
            $page.text: {
                .font = $doc.Font, $doc.size;
                .text-position = $x, $yy;
                .say($pp);
            }

            # start a new page
            $page = $doc.pdf.add-page();
            ++$pnum;
            # reset y
            $y  = $y0;
        }
    }
    # make sure the last page is numbered

    note "DEBUG: finished page $pnum of $npages" if $debug;
    note "DEBUG: actually numbered $pnumbered pages" if $debug and $pnumbered != $npages;
    if $pnum <= $npages and $pnumbered != $npages {
        # add a page number
        ++$pnumbered;
        my $pp;
        if $doc.title {
            $pp = "Page $pnum of $npages ($doc.title)";
        }
        else {
            $pp = "Page $pnum of $npages";
        }
        my $yy = 36; # 1/2 inches from the bottom
        $page.text: {
            .font = $doc.Font, $doc.size;
            .text-position = $x, $yy;
            .say($pp);
        }
    }
    note "DEBUG: have now numbered $pnumbered pages" if $debug and $pnumbered == $npages;

} # make-text-by-lines

=finish
# time trials showed no significant time savings by processing a page of text for four-page docs
sub make-text-by-pages($pdf, @lines, :$debug) {
    # page-by-page (an optimization)

    # determine how many pages:
    my $lines-per-page = (($height - ($top - $bottom)) / $line-spacing).floor;
    my $npages = (@lines.elems / $lines-per-page).ceiling;

    my @pages;
    my $str = '';

    my $pn     = 0;
    my $nlines = 0;
    for @lines -> $line {
        ++$nlines;
        if $nlines > $lines-per-page {
            # put the string of page text away
            @pages.push: $str;
            ++$pn;
            # reinitialize
            $nlines = 0;
            $str = '';
        }
        $str ~= $line;
    }
    if $str {
        @pages.push: $str;
    }
    if @pages.elems != $npages {
        die "FATAL: \$npages ($npages) != \@pages.elems ({@pages.elems})";
    }

    # now convert the pages of text to PDF

    # Add a blank page to start
    my $page = $pdf.add-page();
    my $x  = $left;
    my $y0 = $height - $top - $line-spacing;
    my $y  = $y0;
    my $pnum      = 1; # for page numbering and control
    my $pnumbered = 0; # for page numbering and control
    for @lines -> $line {
        # add the line's text to the page
        $page.text: {
            .font = $doc.Font, $size;
            .text-position = $x, $y;
            .say($line);
        }

        $y -= $line-spacing;
        if $y <= $bottom {
            note "DEBUG: numbering page $pnum of $npages" if $debug;
            # add a page number
            ++$pnumbered;
            my $pp = "Page $pnum of $npages";
            my $yy = 36; # 1/2 inches from the bottom
            $page.text: {
                .font = $doc.Font, $size;
                .text-position = $x, $yy;
                .say($pp);
            }

            # start a new page
            $page = $pdf.add-page();
            ++$pnum;
            # reset y
            $y  = $y0;
        }
    }
    # make sure the last page is numbered

    note "DEBUG: finished page $pnum of $npages" if $debug;
    note "DEBUG: actually numbered $pnumbered pages" if $debug and $pnumbered != $npages;
    if $pnum <= $npages and $pnumbered != $npages {
        # add a page number
        ++$pnumbered;
        my $pp = "Page $pnumbered of $npages";
        my $yy = 36; # 1/2 inches from the bottom
        $page.text: {
            .font = $doc.Font, $size;
            .text-position = $x, $yy;
            .say($pp);
        }
    }
    note "DEBUG: have now numbered $pnumbered pages" if $debug and $pnumbered == $npages;

} # make-text-by-pages
