#!/usr/bin/env perl

use 5.006002;

use strict;
use warnings;

use Astro::SpaceTrack;
use CGI ();
use Cwd ();
use Getopt::Long 2.39;
use JSON;
use Pod::Usage;
use POSIX qw{ strftime };

our $VERSION = '0.167_01';

my %opt;

GetOptions( \%opt,
    qw{ username=s password=s },
    help => sub { pod2usage( { -verbose => 2 } ) },
) or pod2usage( { -verbose => 0 } );

my %desc;

my $cgi = CGI->new();
$cgi->charset( 'utf-8' );
my $json = JSON->new()->utf8();
my $st = Astro::SpaceTrack->new();

foreach my $attr ( qw{ password username } ) {
    defined $opt{$attr}
	and $st->set( $attr => $opt{$attr} );
}

defined $opt{username}
    or defined $opt{password}
    or $st->set( identity => 1 );

my $title = 'Space Track Model Definitions';

print
#   $cgi->header( -charset => 'utf-8' ),
    $cgi->start_html( -title => $title, -style => { src => 'trw.css' } ),
    $cgi->h1( $title ), $cgi->p( <<"EOD" );
This is an expansion on the model definitions provided by version 2 of
the Space Track interface. It is generated by
@{[ Cwd::abs_path( $0 ) ]}
EOD

foreach my $class ( qw{ boxscore launch_site satcat tle tle_latest } ) {
    my $resp = $st->spacetrack_query_v2(
	basicspacedata	=> 'modeldef',
	class		=> $class,
	format		=> 'json',
    );
    $resp->is_success()
	or die $resp->status_line();

    print $cgi->h2( "Class $class" ), $cgi->start_dl();
    foreach my $field (
	sort { $a->{Field} cmp $b->{Field} }
	@{ $json->decode( $resp->content() )->{data} }
    ) {
	my $name = $field->{Field};
	print $cgi->dt( $name );
	my $blurb = defined $desc{$class}{$name} ? $desc{$class}{$name} :
	    defined $desc{_}{$name} ? $desc{_}{$name} : 'Unknown.';
	$blurb =~ s/ \s* \n / /smxg;
	print $cgi->dd( $blurb, interpret_type( $field ) );
    }
    print $cgi->end_dl();
}

print $cgi->p( <<'EOD' );
The descriptions of the data types should be pretty much
self-explanatory. However:
EOD

print $cgi->ul(
    map { $cgi->li( $_ ) } <<'EOD',
<code>date</code> by itself means a calendar date (GMT), without a time
of day. The date is expressed as a numeric <code>year-month-day</code>
with the year being a four-digit Gregorian year, and the month and day
being two digits.
EOD
    <<'EOD',
<code>date and time</code> means both calendar date (GMT) and optional
time of day. The time of day is expressed as
<code>hour:minute:second</code>, with each field having two digits, and
separated from the date by a single space.
EOD
);

print $cgi->table(
    {
	class	=> 'nopadding',
	summary => 'revision information',
    },
    $cgi->Tr(
	$cgi->td( { class => 'right' }, 'Last revision:' ),
	$cgi->td( strftime( '%d-%b-%Y', localtime ) ),
    ),
);

print $cgi->end_html();

{
    my %interp;

    BEGIN {
	%interp = (
	    bigint	=> sub {
		return 'integer of 8 bytes';
	    },
	    char	=> sub {
		my ( $type, $width ) = @_;
		return "string of $width characters";
	    },
	    date	=> sub {
		return 'date';
	    },
	    datetime	=> sub {
		return 'date and time';
	    },
	    decimal	=> sub {
		my ( $type, $width ) = @_;
		( $width, my $decimal ) = split qr{ , }smx, $width;
		return "decimal number of $width digits with $decimal after the decimal";
	    },
	    double	=> sub {
		return 'double-precision number';
	    },
	    enum	=> sub {
		my ( $type, $width ) = @_;
		return "enumeration of $width";
	    },
	    float	=> sub {
		return 'floating-point number';
	    },
	    int		=> sub {
		return 'integer of 4 bytes';
	    },
	    mediumint	=> sub {
		return 'integer of 3 bytes';
	    },
	    smallint	=> sub {
		return 'integer of 2 bytes';
	    },
	    tinyint	=> sub {
		return 'integer of 1 byte';
	    },
	    varchar	=> sub {
		my ( $type, $width ) = @_;
		return "string of up to $width characters";
	    },
	);
    }

    sub interpret_type {
	my ( $field ) = @_;
	my $type = $field->{Type};
	my @desc = ( 'It is' );
	$type =~ s/ \s* unsigned //smx
	    and push @desc, 'unsigned';
	$type =~ m/ \A ( \w+ ) (?: [(] ( [^(]+ ) [)] )? \s* /smx # )
	    or return;
	my ( $kind, $width ) = ( $1, $2 );
	push @desc, $interp{$kind} ?
	    $interp{$kind}->( $kind, $width ) :
	    defined $width ?
	    "$kind($width)" :
	    $kind;
	splice @desc, 1, 0,
	    $desc[1] =~ m/ \A [aeihou] /smx ? 'an' : 'a';
	$desc[-1] .= '.';
	if ( exists $field->{Null} ) {
	    push @desc, $field->{Null} =~ m/ \A yes \z /smxi ?
		'It can be null.' :
		'It can not be null.';
	}
	if ( exists $field->{Default} ) {
	    push @desc, 'The default is';
	    if ( defined $field->{Default} ) {
		if ( '' eq $field->{Default} ) {
		    push @desc, 'the empty string';
		} else {
		    ( my $dflt = $field->{Default} ) =~ s/ " /""/smxg;
		    push @desc, qq<"$dflt">;
		}
	    } else {
		push @desc, 'null';
	    }
	    $desc[-1] .= '.';
	}
	return join ' ', @desc;
    }
}

BEGIN {
    %desc = (
	_	=> {
	    APOGEE	=> <<'EOD',
This is the apogee of the body's orbit in kilometers above the Earth's
surface.
EOD
	    ARG_OF_PERICENTER	=> <<'EOD',
This is the argument of perigee of the body's orbit, in degrees.
EOD
	    BSTAR	=> <<'EOD',
This is the B* drag term in the model of the body's motion, in inverse
Earth radii.
EOD
	    CLASSIFICATION_TYPE	=> <<'EOD',
This is the classification status of the orbital data. In theory it is
either 'C' for 'Classified' or 'U' for 'Unclassified.' In practice you
will only see 'U'.
EOD
	    COUNTRY	=> <<'EOD',
This is an abbreviation for the country or other agency to which the
body belongs. It corresponds to SPADOC_CD in class boxscore.
EOD
	    CURRENT	=> <<'EOD',
This is 'Y' for the current record when the query is made, or 'N'
otherwise.
EOD
	    DECAY	=> <<'EOD',
This is the date the body deorbited. If the body is still in orbit it is
null.
EOD
	    ECCENTRICITY	=> <<'EOD',
This is the eccentricity of the body's orbit.
EOD
	    ELEMENT_SET_NO	=> <<'EOD',
This is the orbital element sets for a given body are numbered
sequentially.  This is this element set's number in the sequence.
EOD
	    EPHEMERIS_TYPE	=> <<'EOD',
In theory, this is a number encoding the model that the data are
intended for. In practice, it is always 0.
EOD
	    EPOCH		=> <<'EOD',
This is the date and time of the epoch of the orbital data. Because of
the desire to be able to query the data, the fractional-second portion
of the epoch is in EPOCH_MICROSECONDS.
EOD
	    EPOCH_MICROSECONDS	=> <<'EOD',
This is the fractional-second portion of the epoch. To get the true
epoch of the data, divide this by one million and add it to the EPOCH.
EOD
	    FILE	=> <<'EOD',
This is, more or less, the transaction number of the most recent Space
Track update that contained this data. It can be used for incremental
updates by saving the FILE from the last download and then selecting
only data with a greater FILE.
EOD
	    INCLINATION	=> <<'EOD',
This is the inclination of the body's orbit in degrees.
EOD
	    INTLDES		=> <<'EOD',
This is the international launch designator. It is made up of the last
two digits of the Gregorian year (with leading zeroes), a three-digit
launch number in the year (with leading zeroes), and a launch piece
which consists of one or more upper-case alphabetic characters, with the
payload being 'A'. For example, the Zarya module of the International
Space Station is 98067A. This is different from the INTLDES in class
satcat.
EOD
	    LAUNCH	=> <<'EOD',
This is the date the body was launched.
EOD
	    LAUNCH_NUM	=> <<'EOD',
This is the launch number in the year.
EOD
	    LAUNCH_PIECE	=> <<'EOD',
This is the launch piece.
EOD
	    LAUNCH_SITE	=> <<'EOD',
This is the name of a launch site.
EOD
	    LAUNCH_YEAR	=> <<'EOD',
This is the Gregorian year the body was launched.
EOD
	    MEAN_ANOMALY	=> <<'EOD',
This is the mean anomaly of the body's orbit, in degrees.
EOD
	    MEAN_MOTION	=> <<'EOD',
This is the mean motion of the body, in revolutions per day.
EOD
	    MEAN_MOTION_DOT	=> <<'EOD',
This is the first time derivative of the mean motion (i.e. the
acceleration), in revolutions per day squared. Also known as the
Ballistic Coefficient.
EOD
	    MEAN_MOTION_DDOT	=> <<'EOD',
This is the second time derivative of the mean motion, in revolutions
per day cubed.
EOD
	    NORAD_CAT_ID	=> <<'EOD',
This is the OID, or NORAD catalog ID. Note that there are no leading
zeroes.
EOD
	    OBJECT_ID		=> <<'EOD',
This is the international launch designator. It is made up of a numeric
four-digit Gregorian year, a dash, a three-digit launch number in the
year (with leading zeroes), and a launch piece which consists of one or
more upper-case alphabetic characters, with the payload being 'A'. For
example, the Zarya module of the International Space Station is
1998-067A.
EOD
	    OBJECT_NAME		=> <<'EOD',
This is the common name of the object.
EOD
	    OBJECT_NUMBER	=> <<'EOD',
This is the OID, or NORAD catalog ID. Note that there are no leading
zeroes.
EOD
	    OBJECT_TYPE	=> <<'EOD',
This is a computed value representing the general type of the body. It
can be 'DEBRIS', 'PAYLOAD', 'ROCKET BODY', 'TBA', 'UNKNOWN', or 'OTHER'.
EOD
	    ORBITAL_TBA		=> <<'EOD',	# boxscore
At a guess, number of objects on-orbit which are not yet classified.
EOD
	    PERIGEE		=> <<'EOD',
This is the perigee of the body's orbit in kilometers above the Earth's
surface.
EOD
	    PERIOD		=> <<'EOD',
This is the period of the body's orbit, in minutes.
EOD
	    RA_OF_ASC_NODE		=> <<'EOD',
This is the right ascension of the ascending node of the body's orbit,
in degrees (not hours).
EOD
	    RCSSOURCE	=> <<'EOD',
This is an abbreviation of the source for the radar cross-section data.
EOD
	    RCSVALUE	=> <<'EOD',
This is the radar cross-section of the body, in square meters. This is
now always carried as 0.
EOD
	    RCS_SIZE	=> <<'EOD',
This is a qualitative statement of the size of the RCS. Observed values
are 'SMALL', 'MEDIUM', 'LARGE', and null.
EOD
	    REV_AT_EPOCH		=> <<'EOD',
This is the number of revolutions the body has made at the epoch time.
In practice, this seems to be a whole-number value.
EOD
	    SEMIMAJOR_AXIS	=> <<'EOD',
This is the semimajor axis of the orbit, in kilometers.
EOD
	    SITE		=> <<'EOD',
This is an abbreviation for the site from which the body was launched.
EOD
	    SITE_CODE		=> <<'EOD',
This is an abbreviation for the site from which the body was launched.
EOD
	    TLE_LINE0		=> <<'EOD',
This is the common name of the object, with a '0 ' prefixed, since it is
the zeroth line of the TLE.
EOD
	    TLE_LINE1		=> <<'EOD',
This is the first line of the TLE data for this body's orbit, as
represented in a traditional TLE data set.
EOD
	    TLE_LINE2		=> <<'EOD',
This is the second line of the TLE data for this body's orbit, as
represented in a traditional TLE data set.
EOD
	},
	boxscore	=> {
	    COUNTRY			=> <<'EOD',
Owning country or agency.
EOD
	    COUNTRY_TOTAL		=> <<'EOD',
Number of bodies launched.
EOD
	    DECAYED_DEBRIS_COUNT	=> <<'EOD',
Number of debris bodies decayed.
EOD
	    DECAYED_PAYLOAD_COUNT	=> <<'EOD',
Number of payloads decayed.
EOD
	    DECAYED_ROCKET_BODY_COUNT	=> <<'EOD',
Number of rocket bodies decayed.
EOD
	    DECAYED_TOTAL_COUNT		=> <<'EOD',
Number of bodies of all types decayed.
EOD
	    ORBITAL_DEBRIS_COUNT	=> <<'EOD',
Number of debris bodies in orbit.
EOD
	    ORBITAL_PAYLOAD_COUNT	=> <<'EOD',
Number of payloads in orbit.
EOD
	    ORBITAL_ROCKET_BODY_COUNT	=> <<'EOD',
Number of rocket bodies in orbit.
EOD
	    ORBITAL_TOTAL_COUNT		=> <<'EOD',
Number of bodies of all types in orbit.
EOD
	    SPADOC_CD			=> <<'EOD',
Abbreviation of owning country or agency.
EOD
	},
	satcat	=> {
	    INTLDES		=> <<'EOD',
This is the international launch designator. It is made up of a numeric
four-digit Gregorian year, a dash, a three-digit launch number in the
year (with leading zeroes), and a launch piece which consists of one or
more upper-case alphabetic characters, with the payload being 'A'. For
example, the Zarya module of the International Space Station is
1998-067A. The individual parts of the international launch designator
are available in the LAUNCH_YEAR, LAUNCH_NUM, and LAUNCH_PIECE fields.
EOD
	    SATNAME		=> <<'EOD',
This is the common name of the satellite.
EOD
	},
	tle		=> {
	    DECAYED		=> <<'EOD',
This is a flag which is set if the body has decaued. It is documented as
being 0 if not.
EOD
	},
	tle_latest	=> {
	    DECAYED		=> <<'EOD',
This is a flag which is set if the body has decaued. It is documented as
being 0 if not.
EOD
	    ORDINAL		=> <<'EOD',
This is the ordinal number of this data set for this OID. The number
increases with decreasing EPOCH, with the most-recent epoch being 1.
EOD
	},
    );

}

__END__

=head1 TITLE

modeldef - Generate a web page describing Space Track models of interest.

=head1 SYNOPSIS

 modeldef
 modeldef -help
 modeldef -version

=head1 OPTIONS

=head2 -help

This option displays the documentation for this script. The script then
exits.

=head2 -password string

This option specifies the password used to access Space Track. The
default comes from environment variable C<SPACETRACK_USER>, or from an
identity file.

=head2 -username string

This option specifies the user name used to access Space Track. The
default comes from environment variable C<SPACETRACK_USER>, or from an
identity file.

=head2 -version

This option displays the version of this script. The script then exits.

=head1 DETAILS


This Perl script generates a web page which describes the Space Track
REST classes of interest.

=head1 AUTHOR

Thomas R. Wyant, III F<wyant at cpan dot org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2012, 2017-2024 by Thomas R. Wyant, III

This program is free software; you can redistribute it and/or modify it
under the same terms as Perl 5.10.0. For more details, see the full text
of the licenses in the directory LICENSES.

This program is distributed in the hope that it will be useful, but
without any warranty; without even the implied warranty of
merchantability or fitness for a particular purpose.

=cut

# ex: set textwidth=72 :
