### ----------------------------------------------------
### -- App::IRC::Log
### -- Licenses: Artistic-2.0
### -- Authors: Elizabeth Mattijsen
### -- File: lib/App/IRC/Log.rakumod
### ----------------------------------------------------
### -- Chunk 2 of 2
 }
            $html
        }
    }

    # Return an IO object for other HTML files in root dir
    multi method html($file --> IO:D) {
        my $template := $file.chop(5) ~ '.crotmp';

        my $html := $!static-dir.add($file);
        my $crot := $!template-dir.add($template);

        # No template, if there's bare HTML, serve it
        if !$crot.e {
            $html.e ?? $html !! Nil
        }

        # May need to render
        else {
            if !$html.e                           # file does not exist
              || $html.modified < $!liftoff       # file is too old
                                | $crot.modified  # or template changed
            {
                self!render: $!static-dir, $html, $crot, {
                  channels => @!channels,
                }
            }
            $html
        }
    }

#--------------------------------------------------------------------------------
# Methods related to routing

    # Stopgap measure until we can ask Cro
    my constant %mime-types = Map.new((
      ''   => 'text/text; charset=UTF-8',
      css  => 'text/css; charset=UTF-8',
      html => 'text/html; charset=UTF-8',
      ico  => 'image/x-icon',
      json => 'text/json; charset=UTF-8',
    ));
    my constant $default-mime-type = %mime-types{''};

    # Return MIME type for a given IO
    sub mime-type(IO:D $io) {
        my $basename := $io.basename;
        with $basename.rindex('.') {
            %mime-types{$basename.substr($_ + 1)}
              // $default-mime-type
        }
        else {
            $default-mime-type
        }
    }

    # Return the clients Accept-Encoding
    sub accept-encoding() {
        with request.headers.first: *.name eq 'Accept-Encoding' {
            .value
        }
        else {
            ''
        }
    }

    # Serve the given IO as a static file
    multi sub serve-static(IO:D $io is copy, *%_) {
        dd $io.absolute;
#        if may-serve-gzip() {
#dd "serving zipped";
#            header 'Transfer-Encoding', 'gzip';
#            static self!gzip($io).absolute, |c, :mime-types({ 'gz' => mime-type($io) });
#            content mime-type($io), gzip($io).slurp(:bin);
#        }
#        else {
                    static $io.absolute, :%mime-types
#        }
    }
    multi sub serve-static($, *%) { not-found }

    # Return whether client accepts gzip
    method !may-serve-gzip() {
        $!zip-dir && accept-encoding.contains('gzip')
    }

    # Return IO for gzipped version of given IO
    method !gzip(IO:D $base-dir, IO:D $io) {
        my $io-absolute := $io.absolute;
        my $path := $io-absolute.substr($base-dir.absolute.chars);
        my $gzip := $!zip-dir.add($path ~ '.gz');
        if !$gzip.e || $gzip.modified < $io.modified {
            my \then := now;
            my $proc := run(
              'gzip', '--stdout', '--force', $io-absolute,
              :bin, :out
            );
            mkdir $gzip.parent;
            $gzip.spurt($proc.out.slurp, :bin);
            note "{ ((now - then) * 1000).Int } msecs zipping";
        }
        $gzip
    }

    # Render given template and parameters
    method !render($crot, %params) {
        my \then := now;
        my $html := render-template $crot, %params;
        note "{ ((now - then) * 1000).Int } msecs rendering $crot.basename()";
        $html
    }

    # Render text of given IO and template and make sure there is a
    # gzipped version for it as well if there's a place to store it
    method !render-to-file(
      IO:D $base-dir, IO:D $file, IO:D $crot, %params
    --> Nil) {


        # Remove cached template if it was changed
        get-template-repository.refresh($crot.absolute)
          if $file.e && $file.modified < $crot.modified;

        $file.parent.mkdir;
        {
            my %warnings is BagHash;
            CONTROL {
                when CX::Warn {
                    %warnings.add(.message);
                    .resume;
                }
            }
            $file.spurt: self!render($crot, %params);
            if %warnings {
                for %warnings.sort(*.key) -> (:key($message), :value($seen)) {
                    note $seen > 1
                      ?? $seen ~ "x $message"
                      !! $message;
                }
            }
        }
        self!gzip($base-dir, $file) if $!zip-dir;
    }

    # Create result given template, parameters and json flag
    method !create-result($crot, %params, $json) {
        $json
          ?? to-json(%params,:!pretty)
          !! self!render($crot, %params)
    }

    # Subsets for routing
    subset CSS  of Str  where *.ends-with('.css');
    subset HTML of Str  where *.ends-with('.html');
    subset LOG  of Str  where *.ends-with('.log');

    subset DAY of HTML where {
        try .IO.basename.substr(0,10).Date
    }
    subset YEAR of Str where {
        try .chars == 4 && .Int
    }
    subset MONTH of Str where {
        try .chars == 7
          && .substr(0,4).Int             # year ok
          && 0 <= .substr(5,2).Int <= 13  # month ok, allow for offset of 1
    }
    subset DATE of Str where {
        try .Date
    }

    # Return the actual Cro application to be served
    method application() {
        subset CHANNEL of Str where { %!clogs{$_}:exists }

        route {
#            after { note .Str }   # show response headers
            get -> {
                redirect "/home.html", :permanent
            }
            get -> 'home.html' {
                serve-static self!home
            }
            get -> 'search.html', :$channel, :%args {
                content 'text/html', self!search($channel, |%args)
            }
            get -> 'search.json', :$channel, :%args {
                content 'text/json', self!search($channel, :json, |%args)
            }
            get -> 'around.html', :%args {
                content 'text/html', self!around(|%args)
            }
            get -> 'around.json', :%args {
                content 'text/json', self!around(:json, |%args)
            }

            get -> CHANNEL $channel {
                redirect "/$channel/index.html", :permanent
            }
            get -> CHANNEL $channel, '' {
                redirect "/$channel/index.html", :permanent
            }
            get -> CHANNEL $channel, 'index.html' {
                serve-static self!index($channel)
            }

            get -> CHANNEL $channel, 'live.html', :%args {
                content 'text/html', self!live($channel, |%args)
            }
            get -> CHANNEL $channel, 'live.json', :%args {
                content 'text/json', self!live($channel, :json, |%args)
            }

            get -> CHANNEL $channel, 'gist.html', :%args {
                content
                  'text/html',
                  self!gist($channel, %args.keys.first)         # XXX
            }
            get -> CHANNEL $channel, 'gist.json', :%args {
                content
                  'text/json',
                  self!gist($channel, %args.keys.first, :json)  # XXX
            }

            get -> CHANNEL $channel, 'scroll-down.html', :%args {
                content 'text/html', self!scroll-down($channel, |%args)
            }
            get -> CHANNEL $channel, 'scroll-down.json', :%args {
                content 'text/json', self!scroll-down($channel, :json, |%args)
            }

            get -> CHANNEL $channel, 'scroll-up.html', :%args {
                content 'text/html', self!scroll-up($channel, |%args)
            }
            get -> CHANNEL $channel, 'scroll-up.json', :%args {
                content 'text/json', self!scroll-up($channel, :json, |%args)
            }

            get -> CHANNEL $channel, 'search.html', :%args {
                content 'text/html', self!search($channel, |%args)
            }

            get -> CHANNEL $channel, 'today' {
                redirect "/$channel/"
                  ~ self.clog($channel).this-date(now.Date.Str)
                  ~ '.html';
            }
            get -> CHANNEL $channel, 'first' {
                redirect "/$channel/"
                  ~ self.clog($channel).dates.head
                  ~ '.html';
            }
            get -> CHANNEL $channel, 'last' {
                redirect "/$channel/"
                  ~ self.clog($channel).dates.tail
                  ~ '.html';
            }
            get -> CHANNEL $channel, 'random' {
                redirect "/$channel/"
                  ~ self.clog($channel).dates.roll
                  ~ '.html';
            }

            get -> CHANNEL $channel, YEAR $year {
                my @dates := self.clog($channel).dates;
                redirect "/$channel/"
                  ~ (@dates[finds @dates, $year] || @dates.tail)
                  ~ '.html';
            }
            get -> CHANNEL $channel, MONTH $month {
                my @dates := self.clog($channel).dates;
                redirect "/$channel/"
                  ~ (@dates[finds @dates, $month] || @dates.tail)
                  ~ '.html';
            }

            get -> CHANNEL $channel, 'prev', DATE $date {
                with self.clog($channel).prev-date($date) -> $prev {
                    redirect "/$channel/$prev.html", :permanent
                }
                else {
                    redirect "/$channel/$date.html"
                }
            }
            get -> CHANNEL $channel, 'this', DATE $date {
                redirect "/$channel/"
                  ~ self.clog($channel).this-date($date.Str)
                  ~ '.html';
            }
            get -> CHANNEL $channel, 'next', DATE $date {
                with self.clog($channel).next-date($date) -> $next {
                    redirect "/$channel/$next.html", :permanent
                }
                else {
                    redirect "/$channel/$date.html"
                }
            }

            get -> CHANNEL $channel, DAY $file {
                my $clog := self.clog($channel);
                my $date := $file.chop(5);
                $clog.log($date)
                  ?? serve-static self!day($channel, $date)
                  !! redirect "/$channel/$clog.this-date($date).html";
            }
            get -> CHANNEL $channel, HTML $file {
                serve-static self.html($channel, $file);
            }
            get -> CHANNEL $channel, LOG $file {
                my $io := $!log-dir
                  .add($channel)
                  .add($file.substr(0,4))
                  .add($file.chop(4));

                serve-static $io
            }
            get -> CHANNEL $channel, $file {
                my $io := $!static-dir.add($channel).add($file);
                serve-static $io.e ?? $io !! $!static-dir.add($file)
            }
            get -> HTML $file {
                serve-static self.html($file)
            }

            get -> $file, :%headers is header {
#say "$_.key() => $_.value()" for %headers.sort(*.key);
                serve-static $!static-dir.add($file)
            }

            get -> |c {
                dd c;
                not-found
            }
        }
    }
}

=begin pod

=head1 NAME

App::IRC::Log - Cro application for presenting IRC logs

=head1 SYNOPSIS

=begin code :lang<raku>

use App::IRC::Log;

my $ail := App::IRC::Log.new:
  :$channel-class,  # IRC::Channel::Log compatible class
  :$log-class,      # IRC::Log compatible class
  :$log-dir,
  :$rendered-dir,
  :$state-dir,
  :$static-dir,
  :$template-dir,
  :$zip-dir,
  colorize-nick => &colorize-nick,
  htmlize       => &htmlize,
  special-entry => &special-entry,
  channels      => @channels,
  live-plugins       => live-plugins(),
  day-plugins        => day-plugins(),
  search-plugins     => search-plugins(),
  gist-plugins       => gist-plugins(),
  scrollup-plugins   => scrollup-plugins(),
  scrolldown-plugins => scrolldown-plugins(),
  descriptions       => %descriptions,
  one-liners         => %one-liners,
  highlight-before   => "<strong>",
  highlight-after    => "</strong>",
;

my $service := Cro::HTTP::Server.new:
  :application($ail.application),
  :$host, :$port,
;
$service.start;

react whenever signal(SIGINT) {
    $service.stop;
    $ail.shutdown;
    exit;
}

=end code

=head1 DESCRIPTION

App::IRC::Log is a class for implementing an application to show IRC logs.

It is still heavily under development and may change its interface at any
time.

It is currently being used to set up a website for showing the historical
IRC logs of the development of the Raku Programming Language (see
C<App::Raku::Log>).

=head1 AUTHOR

Elizabeth Mattijsen <liz@raku.rocks>

Source can be located at: https://github.com/lizmat/App-IRC-Log . Comments and
Pull Requests are welcome.

=head1 COPYRIGHT AND LICENSE

Copyright 2021 Elizabeth Mattijsen

This library is free software; you can redistribute it and/or modify it under the Artistic License 2.0.

=end pod

# vim: expandtab shiftwidth=4


