=head1 NAME

App::MergeCal

=head1 ABSTRACT

Command line program that merges iCal files into a single calendar.

=head1 SYNOPSIS

    use App::MergeCal;

    my $app = App::MergeCal->new;
    $app->run;

    # Or, more likely, use the mergecal program

=cut

use 5.24.0;
use Feature::Compat::Class;

package App::MergeCal; # For PAUSE

class App::MergeCal {

  our $VERSION = '0.0.2';

  use Encode 'encode_utf8';
  use Text::vFile::asData;
  use LWP::Simple;
  use JSON;

  field $vfile_parser :param = Text::vFile::asData->new;
  field $title :param;
  field $output :param = '';
  field $calendars :param;
  field $objects;

=head1 METHODS

=head2 $app->calendars, $app->title, $app->output

Accessors for fields.

=cut

  method calendars { return $calendars }
  method title     { return $title }
  method output    { return $title }

=head2 $app->run

Main driver method.

=cut

  method run {
    $self->gather;
    $self->render;
  }

=head2 $app->gather

Access all of the calendars and gather their contents into $objects.

=cut

  method gather {
    for (@$calendars) {
      my $ics = get($_) or die "Can't get $_";
      $ics = encode_utf8($ics);
      open my $fh, '<', \$ics or die $!;
      my $data = $vfile_parser->parse( $fh );

      push @$objects, @{ $data->{objects}[0]{objects} };
    }
  }

=head2 $app->render

Take all of the objects in $objects and write them to an output file.

=cut

  method render {
    my $combined = {
      type => 'VCALENDAR',
      properties => {
        'X-WR-CALNAME' => [ { value => $title } ],
      },
      objects => $objects,
    };

    my $out_fh;
    if ($output) {
      open $out_fh, '>', $output
        or die "Cannot open output file [$output]: $!\n";
      select $out_fh;
    }

    say "$_\r" for Text::vFile::asData->generate_lines($combined);
  }

=head2 App::MergeCal->new, App::MergeCal->new_from_json, App::MergeCal->new_from_json_file

Constructor methods.

=over 4

=item new

Constructs an object from a hash of attribute/value pairs

=item new_from_json

Constructs an object from a JSON string representing attribute/value pairs.

=item new_from_json_file

Constructs an object from a file containing a JSON string representing
attribute/value pairs.

=back

=cut

  sub new_from_json {
    my $class = shift;
    my ($json) = @_;

    my $data = JSON->new->decode($json);

    return $class->new(%$data);
  }

  sub new_from_json_file {
    my $class = shift;
    my $conf_file = $_[0] // 'config.json';

    open my $conf_fh, '<', $conf_file or die "$conf_file: $!";
    my $json = do { local $/; <$conf_fh> };

    return $class->new_from_json($json);
  }
}

=head1 CONFIGURATION

The behaviour of the program is controlled by a JSON file. The default name
for this file is C<config.json>. The contents of the file will look something
like this:

    {
      "title":"My Combined Calendar",
      "output":"my_calendar.ics",
      "calendars":[
        "https://example.com/some_calendar.ics",
        "https://example.com/some_other_calendar.ics",
      ]
    }

This configuration will read the the calendars from the URLs listed and
combine their contents into a file called C<my_calendar.ics> (which you will
presumably make available on the internet).

The <output> configuration option is optional. If it is omitted, then the
output will be written to C<STDOUT>.

=head1 AUTHOR

Dave Cross <dave@perlhacks.com>

=head1 LICENSE

Copyright (C) 2024, Magnum Solutions Ltd.  All Rights Reserved.

This script is free software; you can redistribute it and/or modify it
under the same terms as Perl itself.

=cut

1;
