#!/usr/bin/env perl
use 5.010;
use strict;
use warnings;
use Digest::SHA qw(sha1_hex);

# leapcompile - compile IERS's leap-seconds.list into the C table
# Time::Keeper ships.
#
#   tools/leapcompile leap-seconds.list > include/tk/tk_leap_data.h
#
# Author side only: the output is committed to the dist.
#
# The list counts seconds from 1900-01-01 (NTP). Each entry is the instant
# a new TAI - UTC takes effect, which for every entry after the first is the
# midnight following an inserted leap second. The first, 1972-01-01 at 10
# seconds, is where UTC as it now is began: it is not a leap second.

my $NTP_TO_POSIX = 2208988800;

my $file = shift or die "usage: $0 leap-seconds.list\n";
open my $fh, '<', $file or die "$file: $!\n";
my (@entries, $expires, $updated, $hash, $hashed);
while (<$fh>) {
    if (/^#@\s+(\d+)/)       { $expires = $1 - $NTP_TO_POSIX; next }
    if (/^#\$\s+(\d+)/)      { $updated = $1 - $NTP_TO_POSIX; next }
    if (/^#h\s+(.*?)\s*$/)   { $hash = $1; next }
    next if /^#/ || !/\S/;
    my ($ntp, $offset) = /^(\d+)\s+(\d+)/ or die "$file line $.: not an entry\n";
    push @entries, [$ntp - $NTP_TO_POSIX, $offset];
    $hashed .= "$ntp$offset";
}
die "$file: no expiry line\n" unless defined $expires;
die "$file: no entries\n" unless @entries;

# IERS signs the data with a SHA-1 over the update time, the expiry time and
# each entry's two numbers, joined with nothing between them, printed as
# five 32-bit words in hex without leading zeros. A list whose hash does not
# match its data has been damaged or edited by hand: refuse it.
{
    open my $raw, '<', $file or die "$file: $!\n";
    my ($u, $x);
    while (<$raw>) { $u = $1 if /^#\$\s+(\d+)/; $x = $1 if /^#@\s+(\d+)/ }
    die "$file: no hash line\n" unless defined $hash;
    my @got = map { hex } unpack '(A8)*', sha1_hex($u . $x . $hashed);
    my @want = map { hex } split ' ', $hash;
    die "$file: its hash does not match its data\n" unless "@got" eq "@want";
}
die "$file: does not start at 10 seconds on 1972-01-01\n"
    unless $entries[0][0] == 63072000 && $entries[0][1] == 10;
for my $i (1 .. $#entries) {
    die "$file: entry $i is not one second more than the last\n"
        unless $entries[$i][1] == $entries[$i - 1][1] + 1;
    die "$file: entry $i is not at a midnight UTC\n" if $entries[$i][0] % 86400;
    die "$file: entries out of order\n" unless $entries[$i][0] > $entries[$i - 1][0];
}

my $n = @entries - 1;
print <<"END";
/* Generated by tools/leapcompile from leap-seconds.list. Do not edit:
 * regenerate. Its hash line: $hash */

#ifndef TK_LEAP_DATA_H
#define TK_LEAP_DATA_H

/* The POSIX second at which each leap second ends: the midnight after the
 * 23:59:60 it added. TAI - UTC is TK_LEAP_BASE before the first, and one
 * more after each. */
#define TK_LEAP_COUNT   $n
#define TK_LEAP_BASE    10
#define TK_LEAP_EPOCH   INT64_C($entries[0][0])
#define TK_LEAP_EXPIRES INT64_C($expires)
#define TK_LEAP_UPDATED INT64_C($updated)

static const int64_t tk_leap_ends[TK_LEAP_COUNT] = {
END
print "    INT64_C($entries[$_][0]),\n" for 1 .. $#entries;
print "};\n\n#endif\n";
