#!/usr/bin/env raku
use v6.d;

use App::FontSample::Config;
use App::FontSample::ConfigFile;
use App::FontSample::FontEntry;
use App::FontSample::PDF;
use App::FontSample::Paper;
use App::FontSample::SampleText;
use PDF::Font::Loader :load-font;

sub usage(Int:D $status = 0 --> Nil) {
    my $out = $status == 0 ?? $*OUT !! $*ERR;

    $out.say: q:to/END/;
    Usage:
      font-sample --config=FILE
      font-sample --font=FILE --output=FILE [options]

    JSON mode:
      --config=FILE        Read one or more sample jobs from a JSON file

    Direct mode required options:
      --font=FILE          OTF or TTF font file
      --output=FILE        Output PDF file

    Direct mode options:
      --name=TEXT          Display name; defaults to font filename
      --family=TEXT        Font family metadata
      --style=TEXT         Font style; default: Regular
      --layout=NAME        specimen, comparison, characters,
                           collection
      --paper=Letter|A4    Paper size; default: Letter
      --landscape          Use landscape orientation
      --reproducible       Generate reproducible PDF metadata and ID
      --margin=POINTS      Margin in PDF points; default: 36
      --language=CODE      Use a registered ISO language pangram
      --text=TEXT          Main sample text
      --pangram=TEXT       Explicit pangram; overrides --language
      --sizes=N,N,...      Waterfall sizes
      --characters=TEXT    Characters used by characters layout
      --columns=N          Columns used by characters layout
      --comparison-size=N  Point size used by comparison layout
      --sample-size=N      Point size used by collection layout
      --leading-ratio=N    Extra collection line-spacing ratio
      --languages          List built-in language codes
      --help               Show this help
    END

    exit $status;
}

my %options;

for @*ARGS -> $arg {
    if $arg eq '--help' or $arg eq '-h' {
        usage;
    }
    elsif $arg eq '--languages' {
        say App::FontSample::SampleText.new.languages.join(' ');
        exit 0;
    }
    elsif $arg eq '--landscape' {
        %options<landscape> = True;
    }
    elsif $arg eq '--reproducible' {
        %options<reproducible> = True;
    }
    elsif $arg ~~ /^ '--' (<[A..Za..z0..9_-]>+) '=' (.*) $/ {
        %options{~$0} = ~$1;
    }
    else {
        $*ERR.say: "Unknown argument: $arg";
        usage 2;
    }
}

if %options<config>:exists {
    die '--config cannot be combined with other options'
        if %options.elems > 1;

    render-config-file(%options<config>);
    exit 0;
}

for <font output> -> $required {
    unless %options{$required}:exists
        and %options{$required}.chars {
        $*ERR.say: "Missing required option --$required";
        usage 2;
    }
}

render-direct-options(%options);

sub render-config-file(IO() $config-file --> Nil) {
    my $definition =
        App::FontSample::ConfigFile.from-file($config-file);

    for $definition.samples.List -> $config {
        render-config($config);
    }
}

sub render-config(
    App::FontSample::Config:D $config
    --> Nil
) {
    my @entries;

    for $config.fonts.List -> $font-data {
        my %font-data = $font-data.Hash;
        my IO::Path $file = %font-data<file>;

        die "Font file does not exist: $file"
            unless $file.f;

        my $font = load-font :file($file.Str);
        my Str $name =
            %font-data<name> // $file.basename;

        @entries.push: App::FontSample::FontEntry.new(
            :$font,
            :$name,
            :family(%font-data<family> // ''),
            :style(%font-data<style> // ''),
            :source(%font-data<source> // $file.Str),
        );
    }

    my $paper = App::FontSample::Paper.new(
        :paper($config.paper),
        :landscape($config.landscape),
        :margin($config.margin),
    );

    my $sampler = App::FontSample::PDF.new(
        :title($config.title),
        :layout($config.layout),
        :paper($paper),
    );

    my %render-options =
        output       => $config.output,
        reproducible => $config.reproducible;

    %render-options<text> = $config.text
        if $config.text.defined;

    %render-options<pangram> = $config.pangram
        if $config.pangram.defined;

    %render-options<language> = $config.language
        if $config.language.defined;

    %render-options<sizes> = $config.sizes
        if $config.sizes.elems;

    %render-options<characters> = $config.characters
        if $config.characters.defined;

    %render-options<comparison-size> =
        $config.comparison-size
        if $config.comparison-size.defined;

    %render-options<sample-size> =
        $config.sample-size
        if $config.sample-size.defined;

    %render-options<leading-ratio> =
        $config.leading-ratio
        if $config.leading-ratio.defined;

    %render-options<columns> = $config.columns
        if $config.columns.defined;

    my $output = $sampler.render-collection(
        @entries,
        |%render-options,
    );

    say "Created $output";
}

sub render-direct-options(%options --> Nil) {
    my IO::Path $font-file = %options<font>.IO;

    die "Font file does not exist: $font-file"
        unless $font-file.f;

    my $font = load-font :file($font-file.Str);
    my Str $name =
        %options<name> // $font-file.basename;

    my Str $paper-name =
        %options<paper> // 'Letter';

    my Numeric $margin =
        (%options<margin> // 36).Numeric;

    my Str $layout =
        %options<layout> // 'specimen';

    my @sizes;

    if %options<sizes>:exists {
        for %options<sizes>.split(',') -> $item {
            my Numeric $size = $item.Numeric;

            die 'Every size must be greater than zero'
                unless $size > 0;

            @sizes.push: $size;
        }
    }

    my Str $pangram = %options<pangram>;

    if $pangram.defined
        and (not %options<language>:exists) {
        die '--pangram requires --language=CODE so the pangram can be labeled';
    }

    if (not $pangram.defined)
        and %options<language>:exists {
        $pangram = App::FontSample::SampleText.new.pangram(
            %options<language>
        );
    }

    my $paper = App::FontSample::Paper.new(
        :paper($paper-name),
        :landscape(%options<landscape> // False),
        :$margin,
    );

    my $sampler = App::FontSample::PDF.new(
        :$layout,
        :paper($paper),
    );

    my %render-options =
        name   => $name,
        family => (%options<family> // ''),
        style  => (%options<style> // ''),
        output => %options<output>,
        reproducible => so (%options<reproducible> // False);

    %render-options<text> = %options<text>
        if %options<text>:exists;

    %render-options<pangram> = $pangram
        if $pangram.defined;

    %render-options<language> = %options<language>
        if %options<language>:exists;

    %render-options<sizes> = @sizes
        if @sizes.elems;

    %render-options<characters> = %options<characters>
        if %options<characters>:exists;

    %render-options<columns> = %options<columns>.Int
        if %options<columns>:exists;

    %render-options<comparison-size> =
        %options<comparison-size>.Numeric
        if %options<comparison-size>:exists;

    %render-options<sample-size> =
        %options<sample-size>.Numeric
        if %options<sample-size>:exists;

    %render-options<leading-ratio> =
        %options<leading-ratio>.Numeric
        if %options<leading-ratio>:exists;

    my $output =
        $sampler.render-font(
            $font,
            |%render-options,
        );

    say "Created $output";
}
