#!/usr/bin/env perl

use utf8;
use 5.008006;
use strict;
use warnings;

use version; our $VERSION = qv('v0.0.6');

use English qw{ −no_match_vars };
use Carp;
use Fatal qw{ open close read write };
use Readonly;

use Getopt::Euclid;

use DBI;
use IO::File;

use Mac::Apps::Seasonality::CommandLineUtilities qw{ :growl :functions };
use Mac::Apps::Seasonality::Constants qw{ :application };
use Mac::Apps::Seasonality::LoadICAOHistoryFromCSV qw{
    %ALLOWED_UNITS
    &load_icao_history_from_csv_handle
};


Readonly my $EMPTY_STRING       => q{};


my $temperature_units = _get_units('--temperature-units', 'temperature_units');
my $pressure_units = _get_units('--pressure-units', 'pressure_units');
my $wind_speed_units = _get_units('--wind-speed-units', 'wind_speed_units');


my $quiet = $ARGV{'--quiet'};
initialize_program_utilities(
    "Load $SEASONALITY_NAME History",
    quiet => $quiet,
    use_growl => ! $ARGV{'--no-growl'},
);

state_progress(
    $GROWL_NOTIFICATION_PROGRESS,
    'Start.'
);


shutdown_seasonality_if_necessary(
    no_shutdown   => $ARGV{'--no-shutdown'},
    no_restart    => $ARGV{'--no-restart'},
    force_start   => $ARGV{'--force-start'},
    shutdown_wait => $ARGV{'--shutdown-wait'},
);


my $database_file_name = $ARGV{'--database'} || $SEASONALITY_HISTORY_DATABASE_PATH;

my $database_connection =
    create_database_connection(
        $database_file_name,
        backup => ! $ARGV{'--no-backup'},
    );

my $clean = $ARGV{'--clean'};
foreach my $file_name ( @{ $ARGV{'<file>'} } ) {
    state_progress(
        $GROWL_NOTIFICATION_PROGRESS,
        "Loading data from \"$file_name\".",
    );

    my $file_handle = IO::File->new($file_name, 'r');
    load_icao_history_from_csv_handle(
        $database_connection,
        $file_handle,
        clean => $clean,
        clean_message_handle => ( ($clean and not $quiet) ? \*STDOUT : undef ),
        temperature_units => $temperature_units,
        pressure_units => $pressure_units,
        wind_speed_units => $wind_speed_units,
    );
    $file_handle->close();
} # end foreach

state_progress(
    $GROWL_NOTIFICATION_PROGRESS,
    'Data loaded.',
);


close_database_connection();


restart_seasonality_if_necessary();

state_progress(
    $GROWL_NOTIFICATION_PROGRESS,
    'Done.',
);


sub _get_units {
    my ($command_line_option, $load_option) = @_;

    my $units = $ARGV{$command_line_option};
    if (
            $units
        and not $ALLOWED_UNITS{$load_option}->{$units}
    ) {
        die
            q{"},
            $units,
            qq{" is not one of the allowed values for the $command_line_option option.},
            ' Permitted values are: "',
            join ('", "', sort keys %{ $ALLOWED_UNITS{$load_option} } ),
            qq{".\n};
    } # end if

    return $units;
} # end _get_units()


__END__

=encoding utf8

=head1 NAME

load_seasonality_history - Load historical weather data into Seasonality's
database.


=head1 VERSION

This document describes load_seasonality_history version 0.0.6.


=head1 USAGE

    load_seasonality_history file... [options]


=head1 OPTIONS

Note: the set of options I<will> change in future versions; do not count upon
the current interface.

=over

=item --clean

Clean up incoming data, treating invalid values as not being present.  At
present, this only handles the numeric columns (e.g. not the date).

Data that is considered invalid includes values that don't look like numbers
and values that are outside the range allowed for the column, e.g. a humidity
greater than 100%.

Non-integer values for integer columns will be rounded, rounding up when the
fractional portion is greater than or equal to 0.5.

=item --temperature-units [=] <units>

The units that the input temperatures are in.  Must be one of C<f>,
C<fahrenheit>, C<c>, C<celsius>.

=for Euclid:
    units.type: string

=for Euclid_bug
    http://rt.cpan.org/Ticket/Display.html?id=27074
    This should be:
    units.type: /f|fahrenheit|c|celsius/
    units.type.error: Units must be one of 'f', 'fahrenheit', 'c', 'celsius'.
    units.default: 'celsius'

=item --pressure-units [=] <units>

The units that the input pressures are in.  Must be one of C<iHg>,
C<inches_Hg>, C<inches_of_mercury>, C<millibars>, C<hectopascals>.

=for Euclid:
    units.type: string

=for Euclid_bug
    http://rt.cpan.org/Ticket/Display.html?id=27074
    This should be:
    units.type: /iHg|inches_Hg|inches_of_mercury|millibars|hectopascals/
    units.type.error: Units must be one of 'iHg', 'inches_Hg', 'inches_of_mercury', 'millibars', 'hectopascals'.
    units.default: 'hectopascals'

=item --wind-speed-units [=] <units>

The units that the input wind speeds are in.  Must be one of C<mph>,
C<miles_per_hour>, C<knots>.

=for Euclid:
    units.type: string

=for Euclid_bug
    http://rt.cpan.org/Ticket/Display.html?id=27074
    This should be:
    units.type: /mph|miles_per_hour|knots/
    units.type.error: Units must be one of 'mph', 'miles_per_hour', 'knots'.
    units.default: 'knots'

=item --database [=] <file>

=for Euclid:
    file.type: readable

Use a database other than Seasonality's default,
C<~/Library/Application Support/Seasonality/weather.db>.

=item --no-shutdown

If Seasonality is running, don't shut it down before processing.

=item --shutdown-wait [=] <seconds>

=for Euclid:
    seconds.default: 5
    seconds.type: integer >= 0

The number of seconds to wait for Seasonality to shut down. Defaults to 5.

=item --no-backup

Don't back up the database before loading the data.

Regardless of the value of this option, the database will not be backed up if
Seasonality is running during the actual load of the data.

=item --no-restart

If Seasonality was running, and it was shut down, don't run it again after the
data is loaded.

=item --force-start

Run Seasonality after the data is loaded, even if it wasn't previously
running.  This option overrides the C<--no-restart> option.

=item -{q|-quiet}

Suppress all output.

=item --no-growl

Even if the L<Mac::Growl> module is installed, don't use it.

=item --version

=item --usage

=item --help

=item --man

Print the usual program information

=back


=head1 REQUIRED ARGUMENTS

=over

=item <file>...

One or more files to load. Each file must be in CSV format.

=for Euclid:
    file.type: readable

=back


=head1 DESCRIPTION

Seasonality is MacOS X weather application, available at
L<http://gauchosoft.com/Software/Seasonality/>. This program provides a means
of getting data into Seasonality's database from data sources that it cannot
handle itself.


=head2 Data

Data is expected in CSV format.  Presently, the values for each data point
must be in the following order:

=over

=item · Location identifier, up to 32 characters.  For custom locations, avoid
using four character identifiers in order to avoid interfering with
Seasonality's standard ICAO airport codes.

=item · UTC/GMT observation time, consisting of year, month, day, hour, and
minute in YYYYMMDDHHMM format.  Seconds are not permitted.

=item · Wind direction, in integer degrees, 0 to 360, or -1 for variable
winds.

=item · Wind speed in integer knots.

=item · Gust speed in integer knots.

=item · Visibility in miles.

=item · Temperature in degrees Celsius.

=item · Dew point in degrees Celsius.

=item · Pressure in integer hectopascals.

=item · Relative humidity in integer percent.

=back

All values are mandatory.  If a measurement is missing, use Seasonality's
marker value of -1000.

No data other than the above is allowed into the file, not even column
headers.

You can update existing data for a given location and observation time;
C<load_seasonality_history> will overwrite any preexisting data.  In
particular, this means that, if you have multiple lines in a file for the same
location and time, the last one wins.


=head2 Paranoia

At present, by default, C<load_seasonality_history> will force Seasonality to
shut down, if it is running, and will create a backup copy of the database
before adding the data.  If Seasonality was running at the start, it will be
relaunched at the end.

In the future, after this program has proved itself reliable, these defaults
will be reversed.


=head1 DEPENDENCIES

L<DBD::SQLite2>
L<Exception::Class>
L<Getopt::Euclid>
L<Mac::Apps::Launch>
L<Mac::Apps::Seasonality::Constants>
L<Mac::Processes>
L<Readonly>
L<Regexp::Common>
L<Text::CSV_XS>
L<version>

Seasonality v1.3 or v1.4.


=head1 DIAGNOSTICS

TODO


=head1 CONFIGURATION AND ENVIRONMENT

C<load_seasonality_history> requires that it is running against a database in
the format used by Seasonality versions 1.3 and 1.4.

The actual Seasonality application must be locatable by L<Mac::Apps::Launch>.


=head1 INCOMPATIBILITIES

None reported.


=head1 BUGS AND LIMITATIONS

=over

=item · The interface to this program is not frozen yet.

=item · Columns are required to be in a fixed order.

=item · Files with column headers or other ignorable data aren't dealt with.

=item · Files are restricted to CSV format.

=item · There's no proper handling of errors. Problems just cause Perl to fall
over.

=item · The DIAGNOSTICS section above is not filled in.

=item · Conversion between dew point and relative humidity is not handled.

=item · The observation date format isn't flexible.

=item · Will not run with databases for Seasonality versions earlier than 1.3.

=back

Please report any bugs or feature requests to
C<bug-mac-apps-seasonality-loadicaohistory@rt.cpan.org>, or through the web
interface at L<http://rt.cpan.org>.


=head1 AUTHOR

Elliot Shank  C<< <perl@galumph.com> >>


=head1 LICENSE AND COPYRIGHT

Copyright ©2006-2007, Elliot Shank C<< <perl@galumph.com> >>. All rights
reserved.

This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself. See L<perlartistic>.


=head1 DISCLAIMER OF WARRANTY

BECAUSE THIS SOFTWARE IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE
SOFTWARE, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE
STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE
SOFTWARE "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND
PERFORMANCE OF THE SOFTWARE IS WITH YOU. SHOULD THE SOFTWARE PROVE DEFECTIVE,
YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR, OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY
COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE
SOFTWARE AS PERMITTED BY THE ABOVE LICENCE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE SOFTWARE (INCLUDING BUT NOT LIMITED TO
LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR
THIRD PARTIES OR A FAILURE OF THE SOFTWARE TO OPERATE WITH ANY OTHER
SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.

=cut

# setup vim: set filetype=perl tabstop=4 softtabstop=4 expandtab :
# setup vim: set shiftwidth=4 shiftround textwidth=78 nowrap autoindent :
# setup vim: set foldmethod=indent foldlevel=0 :
