#!/perl

use 5.22.0;
use strict;
no warnings qw/experimental/;
use feature qw/state switch/;

use utf8;
use FindBin qw/$Bin/;
use lib ("$Bin/../lib");
use Getopt::Long qw/GetOptions/;
use Pod::Usage qw/pod2usage/;
use DateTime;
use Term::ANSIColor;

use Readonly;

use Astro::Montenbruck::Time qw/jd_cent jd2lst $SEC_PER_CEN jd2unix/;
use Astro::Montenbruck::Time::DeltaT qw/delta_t/;
use Astro::Montenbruck::MathUtils qw/frac hms/;
use Astro::Montenbruck::CoCo qw/:all/;
use Astro::Montenbruck::NutEqu qw/obliquity/;
use Astro::Montenbruck::Ephemeris qw/find_positions/;
use Astro::Montenbruck::Ephemeris::Planet qw/@PLANETS/;
use Astro::Montenbruck::Utils::Helpers qw/
    parse_datetime parse_geocoords format_geo hms_str dms_or_dec_str dmsz_str local_now
    @DEFAULT_PLACE/;
use Astro::Montenbruck::Utils::Theme;

sub ecliptic_to_horizontal {
    my ($lambda, $beta, $eps, $lst, $theta) = @_;
    my ($alpha, $delta) = ecl2equ( $lambda, $beta, $eps );
    my $h = $lst * 15 - $alpha; # hour angle, arc-degrees
    equ2hor( $h, $delta, $theta);
}

sub convert_lambda {
    my ($target, $dec) = @_;

    given ($target) {
        sub { dms_or_dec_str( $_[0], decimal => $dec ) }
            when 1;
        sub { dmsz_str( $_[0], decimal => $dec ) }
            when 2;
        sub {
            my ($alpha) = ecl2equ( @_[0..2] );
            hms_str( $alpha / 15, decimal => $dec )
        }   when 3;
        sub {
            my ($alpha) = ecl2equ( @_[0..2] );
            dms_or_dec_str( $alpha, decimal => $dec )
        }   when 4;
        sub {
            my ( $az ) = ecliptic_to_horizontal(@_);
            hms_str( $az / 15, decimal => $dec )
        }   when 5;
        sub {
            my ( $az ) = ecliptic_to_horizontal(@_);
            dms_or_dec_str( $az, decimal => $dec )
        }   when 6;
    }
}

sub convert_beta {
    my ($target, $dec) = @_;

    my $format = sub {
        dms_or_dec_str($_[0], decimal => $dec, places => 2, sign => 1)
    };

    given( $target ) {
        sub {  $format->( $_[1] ) }
            when [1, 2];
        sub {
            my ($alpha, $delta) = ecl2equ( @_[0..2] );
            $format->( $delta )
        }  when [3, 4];
        sub {
            my ( $az, $alt ) = ecliptic_to_horizontal(@_);
            $format->( $alt )
        }  when [5, 6];
    }
}


sub print_position {
    my ($id, $lambda, $beta, $delta, $motion, $obliq, $lst, $lat, $format, $coords, $theme) = @_;
    my $decimal = uc $format eq 'D';
    my $scheme = $theme->scheme;

    state $convert_lambda = convert_lambda($coords, $decimal);
    state $convert_beta   = convert_beta($coords, $decimal);
    state $format_motion  = sub {
        dms_or_dec_str($_[0], decimal => uc $format eq 'D', places => 2, sign => 1 );
    };

    print $theme->decorate( sprintf('%-10s', $id), $scheme->{table_row_title} );
    print $theme->decorate( $convert_lambda->($lambda, $beta, $obliq, $lst, $lat), $scheme->{table_row_data} );
    print "   ";
    print $theme->decorate( $convert_beta->($lambda, $beta, $obliq, $lst, $lat), $scheme->{table_row_data} );
    print "   ";
    print $theme->decorate( sprintf( '%07.4f', $delta ), $scheme->{table_row_data} );
    print "   ";
    print $theme->decorate( $format_motion->($motion), $scheme->{table_row_data} );
    print "\n";
}

sub print_header {
    my ($target, $format, $theme) = @_;
    my $fmt = uc $format;
    my $tmpl;
    my @titles;
    given ($target) {
        when (1) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-11s   %-10s  %-10s %-10s'
                                : '%-7s   %-8s   %-7s  %-10s %-10s';
            @titles = qw/planet lambda beta dist motion/
        }
        when (2) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-11s   %-10s  %-10s %-10s'
                                : '%-7s   %-10s   %-7s  %-10s %-10s';
            @titles = qw/planet zodiac beta dist motion/
        }
        when (3) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-9s   %-10s  %-10s %-10s'
                                : '%-7s   %-6s   %-7s  %-10s %-10s';
            @titles = qw/planet alpha delta dist motion/
        }
        when (4) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-11s   %-10s  %-10s %-10s'
                                : '%-7s   %-8s   %-7s  %-10s %-10s';
            @titles = qw/planet alpha delta dist motion/
        }
        when (5) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-10s  %-9s   %-8s   %-10s'
                                : '%-7s   %-7s  %-6s   %-8s   %-10s';
            @titles = qw/planet azim alt dist motion/
        }
        when (6) {
            $tmpl = $fmt eq 'S' ? '%-7s   %-11s   %-9s   %-8s   %-10s'
                                : '%-7s   %-8s   %-6s   %-8s   %-10s';
            @titles = qw/planet azim alt dist motion/
        }
    }
    say $theme->decorate( sprintf($tmpl, @titles), $theme->scheme->{table_col_title} )
}

my $man    = 0;
my $help   = 0;
my $use_dt = 1;
my $time   = local_now()->strftime('%F %T');
my @place;
my $format = 'S';
my $coords = 1;
my $theme;

# Parse options and print usage if there is a syntax error,
# or if usage was explicitly requested.
GetOptions(
    'help|?'        => \$help,
    'man'           => \$man,
    'time:s'        => \$time,
    'place:s{2}'    => \@place,
    'dt!'           => \$use_dt,
    'format:s'      => \$format,
    'coordinates:i' => \$coords,
    'theme:s'       => 
      sub { $theme = Astro::Montenbruck::Utils::Theme->create( $_[1] ) },
    'no-colors'  => 
      sub { $theme = Astro::Montenbruck::Utils::Theme->create('colorless') }

) or pod2usage(2);

pod2usage(1) if $help;
pod2usage(-verbose => 2) if $man;

# Initialize default options
$theme //= Astro::Montenbruck::Utils::Theme->create('dark');
die "Unknown coordinates format: \"$format\"!" unless $format =~ /^D|S$/i;

@place = @DEFAULT_PLACE unless @place;

my $local = parse_datetime($time);
$theme->print_data('Local Time', $local->strftime('%F %T %Z'));
my $utc;
if ($local->time_zone ne 'UTC') {
    $utc   = $local->clone->set_time_zone('UTC');
} else {
    $utc = $local;
}
$theme->print_data('Universal Time', $utc->strftime('%F %T'));
$theme->print_data('Julian Day', sprintf('%.11f', $utc->jd));

my $t = jd_cent($utc->jd);
if ($use_dt) {
    # Universal -> Dynamic Time
    my $delta_t = delta_t($utc->jd);
    $theme->print_data('Delta-T', sprintf('%05.2fs.', $delta_t));
    $t += $delta_t / $SEC_PER_CEN;
}

my ($lat, $lon) = parse_geocoords(@place);
$theme->print_data('Place', format_geo($lat, $lon));

# Local Sidereal Time
my $lst = jd2lst($utc->jd, $lon);
$theme->print_data('Sidereal Time', hms_str($lst));

# Ecliptic obliquity
my $obliq = obliquity($t);
$theme->print_data(
    'Ecliptic Obliquity',
    dms_or_dec_str(
        $obliq,
        places  => 2,
        sign    => 1,
        decimal => $format eq 'D'
    )
);
print "\n";

print_header($coords, $format, $theme);
find_positions(
    $t,
    \@PLANETS,
    sub { print_position(@_, $obliq, $lst, $lat, $format, $coords, $theme) },
    with_motion => 1
);
print "\n";



__END__

=pod

=encoding UTF-8

=head1 NAME

planpos - calculate planetary positions for given time and place.

=head1 SYNOPSIS

  planpos [options]

=head1 OPTIONS

=over 4

=item B<--help>

Prints a brief help message and exits.

=item B<--man>

Prints the manual page and exits.

=item B<--time>

Date and time, either a I<calendar entry> in format C<YYYY-MM-DD HH:MM Z>, or
C<YYYY-MM-DD HH:MM Z>, or a floating-point I<Julian Day>:

  --time="2019-06-08 12:00 +0300"
  --time="2019-06-08 09:00 UTC"
  --time=2458642.875

Calendar entries should be enclosed in quotation marks. Optional B<"Z"> stands for
time zone, short name or offset from UTC. C<"+00300"> in the example above means
I<"3 hours east of Greenwich">.

=item B<--place>

The observer's location. Contains 2 elements, space separated. 

=over

=item * 

latitude in C<DD(N|S)MM> format, B<N> for North, B<S> for South.

=item *

longitude in C<DDD(W|E)MM> format, B<W> for West, B<E> for East.

=back

E.g.: C<--place=51N28 0W0> for I<Greenwich, UK> (the default).

B<Decimal numbers> are also supported. In that case

=over

=item * 

The latitude always goes first

=item * 

Negative numbers represent I<South> latitude and I<East> longitudes. 

=back

C<--place=55.75 -37.58> for I<Moscow, Russian Federation>.
C<--place=40.73 73.935> for I<New-York, NY, USA>.


=item B<--coordinates>: type and format of coordinates to display:

=over

=item * 

B<1> - Ecliptical, angular units (default)

=item * 

B<2> - Ecliptical, zodiac

=item * 

B<3> - Equatorial, time units

=item * 

B<4> - Equatorial, angular units

=item * 

B<5> - Horizontal, time units

=item * 

B<6> - Horizontal, angular units

=back

=item B<--format>: format of numbers:

=over

=item * 

B<D> decimal: arc-degrees or hours

=item * 

B<S> sexagesimal: degrees (hours), minutes, seconds

=back

=item B<--theme>: color theme:

=over

=item * 

B<dark> (default): for dark consoles

=item * 

B<light>: for light consoles

=item * 

B<colorless>: without colors, for terminals that do not support ANSI color codes

=back

=item B<--no-colors>: do not use colors, same as C<--theme=colorless>

=back



=head1 DESCRIPTION

B<planpos> computes planetary positions for current moment or given
time and place.


=cut
