#!/usr/bin/env perl

use warnings;
use strict;
use v5.10;

use Getopt::Long;
use File::Slurper qw(read_text);
use File::HomeDir;

use Sweat;

my %options;
GetOptions ( \%options,
    'help',
    'version',
    'chair!',
    'jumping!',
    'speech-program=s',
    'config=s',
    'entertainment!',
    'shuffle!',
    'url-program=s',
    'fortune-program=s',
    'newsapi-key=s',
    'country=s',
    'no-news',
    'drill-length=i',
    'rest-length=i',
    'drill-count=i',
);

show_help_and_exit() if $options{help};
show_version_and_exit() if $options{version};
my $config = get_config();

my %sweat_args = %options;

$sweat_args{ config } = $config;

my $sweat = Sweat->new( %sweat_args );

$sweat->sweat;

sub show_help_and_exit {
    show_version();
    say q{};
    say <<END;
Usage:
$0 [options]

For more thorough documentation, please run `man sweat`.

Basic options:
--config           Path to a Sweat config file
--help             Show this screen and exit
--version          Show version information and exit
--speech-program   Path to your text-to-speech program
--no-entertainment Disable Sweat's various entertainment features

Drill options:
--drill-length     Length of each drill, in seconds (default 30)
--rest-length      Length of rests between drills, in seconds (default 10)
--shuffle          Randomize drills (to a certain extent)
--no-shuffle       Force-disable shuffle, overriding config file
--no-chair         Disallow drills that need a chair
--no-jumping       Disallow drills involiving jumping or stomping

Entertainment options:
--fortune-program  Path to your fortune program
--newsapi_key      Your NewsAPI application key
--country          Two-letter code of country to fetch articles from
--no-news          Use Wikipedia as article source even if NewsAPI is available
--url-program      Path to a program that opens a URL in your default browser

Examples:
$0
$0 --shuffle
$0 --no-chair --speech-program=espeak
$0 --config=/path/to/sweat/config --no-news

For more thorough documentation, please run `man sweat`.

END
    exit;
}

sub show_version_and_exit {
    show_version();
    exit;
}

sub show_version {
    say "This is Sweat, version $Sweat::VERSION, by Jason McIntosh.";
}

sub get_config {
    my $config;
    my $config_path = $options{config};
    if ( $config_path ) {
        unless (-r $config_path) {
            die "Can't read config file at $config_path.\n";
        }
    }
    else {
        $config_path = File::HomeDir->my_home . '/.sweat';
    }

    if ( -r $config_path ) {
        $config = read_text( $config_path );
    }
    return $config;
}

=head1 NAME

sweat - a chatty, distracting, and flexible workout timer.

=head1 SYNOPSIS

Run through a seven-minute workout while your computer reads trivia from
Wikipedia, reports on the weather, and tells dumb jokes:

 sweat

Run through a seven-minute workout, but without any of that other stuff:

 sweat --no-entertainment

Run through a seven-minute workout with semi-randomized drills:

 sweat --shuffle

Read in a configuration file and apply its settings:

 sweat --config=/path/to/sweat.config

Force the program to I<not> shuffle, overriding configuration-file settings:

 sweat --no-shuffle

Have the program read news headlines instead of clicking around Wikipedia:

 sweat --newsapi-key=MyNewsApiKey12345

See a quick reference of all command-line options:

 sweat --help

=head1 DESCRIPTION

Sweat is a workout timer that helps distract you from the pain of
exercise by chatting incessantly, using your computer's text-to-speech capabilities to read news headlines, crack
strange old-man jokes, talk about the weather, and still manage to call out
workout prompts when necessary.

Sweat is optimized for the so-called Seven-Minute Workout (7MW), which
leads you through twelve 30-second drills, with 10-second rests in
between. These focus on four types of exercise: aerobic, lower-body,
upper-body, and core. While it has sensible and widely accepted
defaults, you can change or expand its list of drills if you really
want, or adjust how many drills each workout entails, or the timing
involved.

Sweat features a friendly pause function, and a shuffle mode that will
randomize the drills you receive within each type while keeping the types themselves in order,
ensuring you get a varied and balanced workout.

Sweat assumes you already know how to perform the drills it calls out (see L<"The Seven-Minute Workout">, below),
and trusts you to get through them in whatever way works best for you.
Sweat will never judge you.

Yes, Sweat is a command-line program. Get your butt off the chair once
in a while and onto the floor, fellow hackers. It's good for you.

=head2 How Sweat is different from other timers

Sweat's major features that most other 7MW timers don't have:

=head3 A speech-centered UI

Sweat guides you through voice alone (with a simple text transcription in its terminal window).

=head3 Chatty entertainment while you struggle

Sweat will (unless you ask it not to) click through a randomly chosen thread of related Wikipedia articles while you work out and read aloud what it finds.
The workout takes priority, so this intentionally distracting chatter will not make you miss any exercise cues.

With a little extra configuration, you can have Sweat instead read you current news headlines from a variety of sources.

Sweat will also tell you the local weather during certain drills, and end by reading aloud the output of the `fortune` program (if available).

=head3 Optional drill-shuffling

For variety's sake, you can shuffle the drills out of their standard order. While you still get three rounds of aerobic, lower-body, upper-body, and core drills in that order, Sweat will randomize the order of the three drills within each category.

=head3 Pause between side-switching

Halfway through the side-plank drill, Sweat gives you a few seconds to adjust yourself.
(For some reason, few if any other timers seems to bother with this.)

=head3 No-chair and no-jumping modes

A no-chair mode substitutes other drills when you find yourself in a space (e.g. a hotel room) with no suitably stable chair to exercise with.

A no-jumping mode is also available when you want to avoid stomping around on your downstairs neighbor's ceiling.

=head3 Lots of configuration options

It's a command-line Unixish program, so of course it's far too configurable. Happily, its defaults should fit most needs...

=head2 The Seven-Minute Workout

If you're not already familiar with 7MW and its twelve drills, L<this New York Times article may serve as an excellent introduction|https://well.blogs.nytimes.com/2013/05/09/the-scientific-7-minute-workout/>.

As that article suggests, oodles of apps and websites exist for all your mobile and desktop devices
to help guide you through 7MW. Some of them will work better than Sweat at making you familiar
with the exercises involved; you may find it helpful to check them out first.

L<You may also wish to read thoughts by Sweat's author about 7MW|https://fogknife.com/2015-01-11-seven-minute-workout.html>.

=head2 Sweat is not a doctor

B<Please exercise responsibly.> Sweat wants to challenge you, but please do not push yourself too hard. If you start feeling
bad in any way while using Sweat, I<stop immediately>. Consult an actual doctor and not
a weird program you found on the internet with any questions or concerns you have about
setting up an exercise regimen for yourself.

=head1 RUNNING SWEAT

=head2 Pausing the workout

With the window running Sweat focused, hit any key (except for control
keys) to pause Sweat; it will click off its timer and stop talking. Hit
a key again to resume the workout.

Pause whenever you need to, or if you need Sweat to shut up for a second
so you can pay attention to something else.

=head2 Ending the workout early

You can bail out of your workout early by just quitting the program in
an ordinary way; Control-C will work on most systems.

Quit whenever you need to, for any reason. Sweat will never judge you.
It will always greet you upon your return with unfeigned gladness.

=head1 OPTIONS AND CONFIGURATION

Except where otherwise noted, you can set any of these options on the command line, or in a configuration file. See L<"SYNOPSIS">, above, for a few
examples on running sweat with command-line options, or see L<"Configuration file">, below,
for more information on that topic.

B<For boolean options> (such as C<shuffle>), simply name them on the command line to invoke
them: C<--shuffle>, for example. To negate on the command like, precede with "no-", e.g.
C<--no-shuffle>. To set them in a configuration file, set them to 0 or 1 with YAML
syntax: C<shuffle: 1>.

B<For other options>, set them on the command line with an equals sign
(--newsapi_key=MySecretKey), or in a config file with YAML syntax
(C<newsapi_key: MySecretKey>).

Note that you can try running Sweat without any options at all; most settings have
sensible defaults, depending upon your operating system. If sweat requires settings or
other resources that it can't find, it will tell you on startup.

=head2 Basic options

=head3 entertainment

 # On the command line
 sweat --entertainment
 sweat --no-entertainment

 # In config
 entertainment: 0

B<Boolean.> Allow Sweat to entertain you during the workout by reading articles fetched over
the internet, the current weather, and other stuff.

See L<"Entertainment options"> to fine-tune this behavior.

Default: 1

=head3 speech-program

 # Example: Use the 'Victoria' voice with the Mac `say` program...
 # On the command line
 sweat --speech-program='say -v Victoria'

 # In config
 speech-program: say -v Victoria

A valid command-line invocation of your computer's text-to-speech program, including any
command-line arguments you may wish to include. It must take an arbitrary string of text
as its main argument. Sweat will invoke this program every time it wishes to say something.

Default: On Mac, it will default to using "say", a program that comes with the OS. On
other systems, it will try "espeak", a free and open-source program that you may
need to install first. You can find espeak in various package managers, or at
L<http://espeak.sourceforge.net>.

=head2 Drill options

=head3 drill-count

 # On the command line
 sweat --drill-count=10

 # In config
 drill-count: 10

The number of drills to include in the workout.

If Sweat runs through every available drill before meeting this number, then
it will start a new set, continuing until it meets this count.

Default: 12

B<Please note> that you should not work through a large number of drills without mixing
in some longer rest periods. Consider simply running Sweat multiple times, resting
in between, rather than setting this number to something unusually large. (And run in
C<--no-news> mode if you don't want to hear the same headlines over and over.)

=head3 drill-length

 # On the command line
 sweat --drill-length=20

 # In config
 drill-length: 20

The length of each drill, in seconds.

Note that drills that involve side-switching (i.e. side plank in 7MW) will
divide this in half for each side, with a short break in between.

Default: 30


=head3 chair

 # On the command line
 sweat --no-chair

 # In config
 chair: 0

B<Boolean.> Indicates the availability of a chair, for certain drills.

If false, then drills requiring a chair will
replace themselves with a random drill of the same style. (For example, a chairsome
aerobic drill will get replaced with another, less chairish aerobic drill.)

Default: 1

=head3 jumping

 # On the command line
 sweat --no-jumping

 # In config
 jumping: 0

B<Boolean.> Indicates the tolerability of jumping, for certain drills.

If false, then drills involving jumping or stomping will replace
themselves with a random drill of the same style.

=head3 rest-length

 # On the command line
 sweat --rest-length=20

 # In config
 rest-length: 20

The length of the rest period between drills, in seconds.

This includes the preparatory period before the first drill.

Default: 10

=head3 shuffle

 # On the command line
 sweat --shuffle
 sweat --no-shuffle

 # In config
 shuffle: 1

B<Boolean.> Shuffle the drills before presenting them. It will still present three
sets of four drills in the same style-order (aerobic, then lower-body,
then upper-body, and finally core).

Default: 0 (No shuffling)


=head2 Entertainment options

These options come into play only while running Sweat in entertainment mode
(see L<"entertainment">, above).


=head3 country

 # On the command line
 sweat --country=fr

 # In config
 country: fr

The two-letter code for the country that NewsAPI will fetch its headlines from.

Default: us

=head3 fortune-program

 # Example: Run fortune in 'offensive-joke' mode
 # On the command line
 sweat --fortune-program='fortune -o'

 # In config
 fortune-program=fortune -o

A valid command-line invocation (including any desired options) for a program that will say something witty (according to a typical Unix system administrator in 1988).
When in entertainment mode, Sweat will invoke this at the end of every workout,
reading the results out loud.

Default: fortune

=head3 newsapi-key

 # On the command line
 sweat --newsapi-key=MySecretNewsApiKey

 # In config
 drill-length: MySecretNewsApiKey

An application key for NewsAPI. If provided with a valid key, then Sweat will
fetch, read, and display top news headlines from a variety of sources during
your workout (unless run in C<no-news> mode).

You can fetch a free key for personal use at L<https://newsapi.org>.

If I<not> set, then Sweat will read and display Wikipedia articles instead.

Default: none

=head3 url-program

 # Example: On Mac, always open URLs with Firefox:
 # On the command line
 sweat --url-program='open -a Firefox'

 # In config
 url-program: open -a Firefox

A valid command-line invocation (including any desired options) for a program that opens a URL (provided as a main
argument) in a browser.

Default: On Mac, it will default to "open", a program that comes with the OS.
On other systems it has no default (and thus will not open any URLs.)

Non-Mac systems running Sweat may wish to try "xdg-open", a free and open-source program that you may
need to install first. You can find xdg-open as part of "xdg-utils" in various package managers, or at
L<https://freedesktop.org/wiki/Software/xdg-utils/>. Be aware that Sweat currently does not work well if it opens
a terminal-based browser such as Lynx.

=head2 Command-line-only options

These options work only on the command line (and have no effect in a config file).

=head3 config

 sweat --config=/path/to/sweat.config

Full path to a Sweat configuration file, in L<YAML> format.

Default: C<.sweat>, in your home directory. (i.e. C<$HOME/.sweat>)

=head3 help

 sweat --help

Prints a quick reference to Sweat's command-line options, then exits.

=head3 no-news

 sweat --no-news

Forces Sweat to use Wikipedia articles as its main source of workout chatter,
even if a valid NewsAPI key has been provided (via L<"newsapi_key">).

=head3 version

 sweat --version

Prints version and authorship information about Sweat, then exits.

=head2 Configuration file

You can optionally provide Sweat with a configuration file in YAML format. Sweat
will load this on startup, and apply all its settings I<before> applying any settings
provided on the command line (thus letting you override config-file settings that way).

You can provide a path to such a file via the L<"config"> command-line option. If
you don't, Sweat will look for a config file at C<$HOME/.sweat>. If it doesn't
find one there either, it will continue without loading a config file.

If the config file exists but Sweat's YAML parser can't work with it, Sweat will
complain and then exit.

In the config file, you can set any of the options listed under L<"Basic options">
or L<"Entertainment options">, using YAML syntax.

The configuration file also gives you the opportunity to redefine Sweat's drill-list.
You can accomplish this by defining a C<groups> attribute, which contains several
groups of related drills.

Please allow me to explain further by simply showing the definition for Sweat's
default drills, which demonstrate all the special attributes meaningful to
individual drill definitions.

 groups:
  - name: aerobic
    drills:
      - name: jumping jacks
        requires_jumping: 1
      - name: high knees
        requires_jumping: 1
      - name: step-ups
        requires_a_chair: 1
  - name: lower-body
    drills:
      - name: wall sit
      - name: squats
      - name: knee lunges
  - name: upper-body
    drills:
      - name: push-ups
      - name: tricep dips
        requires_a_chair: 1
      - name: rotational push-ups
  - name: core
    drills:
      - name: abdominal crunches
      - name: plank
      - name: side plank
        requires_side_switching: 1

Please note that the C<groups> attribute is not additive; if you define it at all,
then you must define I<all> the drills and drill-groups that Sweat will use.

=head1 NOTES AND BUGS

=head2 Known issues

Sweat is prone to quit unexpectedly if run as the child of another process.

Sweat doesn't play well with terminal-based browsers, such as Lynx.

=head2 Other notes

While the author wishes Sweat to work well on any Unix-like OS, the circumstances
of its development mean that it will likely work best on Mac, at least for now. Consider me
eager to receive reports of how well it works on any system, and welcoming patches and
other suggestions for improving it anywhere.

The default drills specify "knee lunges" and "abdominal crunches" because the words "lunges"
and "crunches" sound too similar, without further context.

=head1 AUTHOR

Jason McIntosh <jmac@jmac.org>

=head1 COPYRIGHT AND LICENSE

This software is Copyright (c) 2019 by Jason McIntosh.

This is free software, licensed under:

  The MIT (X11) License

=head1 A PERSONAL REQUEST

My ability to share and maintain free, open-source software like this
depends upon my living in a society that allows me the free time and
personal liberty to create work benefiting people other than just myself
or my immediate family. I recognize that I got a head start on this due
to an accident of birth, and I strive to convert some of my unclaimed
time and attention into work that, I hope, gives back to society in some
small way.

Worryingly, I find myself today living in a country experiencing a
profound and unwelcome political upheaval, with its already flawed
democracy under grave threat from powerful authoritarian elements. These
powers wish to undermine this society, remolding it according to their
deeply cynical and strictly zero-sum philosophies, where nobody can gain
without someone else losing.

Free and open-source software has no place in such a world. As such,
these autocrats' further ascension would have a deleterious effect on my
ability to continue working for the public good.

Therefore, if you would like to financially support my work, I would ask
you to consider a donation to one of the following causes. It would mean
a lot to me if you did. (You can tell me about it if you'd like to, but
you don't have to.)

=over

=item *

L<The American Civil Liberties Union|https://aclu.org>

=item *

L<The Democratic National Committee|https://democrats.org>

=item *

L<Earthjustice|https://earthjustice.org>

=back

If these words do move you to make a donation of at least $10 to any nonprofit making the world better, and you let me
know about it, I will mail you sticker as a token of gratitude. See
L<this article on my blog|https://fogknife.com/2019-08-03-how-to-support-fogknife.html> for
more information.
