#! /usr/bin/perl
# Written by Zack Weinberg <zackw at panix.com> in 2017.
# To the extent possible under law, Zack Weinberg has waived all
# copyright and related or neighboring rights to this work.
#
# See https://creativecommons.org/publicdomain/zero/1.0/ for further
# details.

# Generate (x)crypt.h from (x)crypt.h.in.

use v5.14;    # implicit use strict, use feature ':5.14'
use warnings FATAL => 'all';
use utf8;
use open qw(:std :utf8);
no  if $] >= 5.022, warnings => 'experimental::re_strict';
use if $] >= 5.022, re       => 'strict';

use FindBin ();
use lib $FindBin::Bin;
use BuildCommon qw(
    enabled_set
    parse_hashes_conf
);

# Substitutions we know about are accumulated in this hash.
my %substs;

sub process_config_h {
    my ($config_h) = @_;
    open my $fh, '<', $config_h
        or die "$config_h: $!\n";

    my $have_sys_cdefs_h               = 0;
    my $have_sys_cdefs_begin_end_decls = 0;
    my $have_sys_cdefs_throw           = 0;
    local $_;
    while (<$fh>) {
        chomp;

        if ($_ eq '#define HAVE_SYS_CDEFS_H 1') {
            $have_sys_cdefs_h = 1;
        }
        elsif ($_ eq '#define HAVE_SYS_CDEFS_BEGIN_END_DECLS 1') {
            $have_sys_cdefs_begin_end_decls = 1;
        }
        elsif ($_ eq '#define HAVE_SYS_CDEFS_THROW 1') {
            $have_sys_cdefs_throw = 1;
        }
        elsif (/^#define PACKAGE_VERSION "((\d+)\.(\d+)\.\d+)"$/) {
            $substs{XCRYPT_VERSION_STR}   = $1;
            $substs{XCRYPT_VERSION_MAJOR} = $2;
            $substs{XCRYPT_VERSION_MINOR} = $3;
        }
    }

    die "$config_h: error: inconsistent <sys/cdefs.h> settings\n"
        if !$have_sys_cdefs_h
        && ($have_sys_cdefs_begin_end_decls || $have_sys_cdefs_throw);

    my $s = q{};
    my $e = q{};
    $s .= "#include <sys/cdefs.h>\n" if $have_sys_cdefs_h;
    $s .= "#define __THROW /* nothing */\n" unless $have_sys_cdefs_throw;
    $s .= "\n";
    if ($have_sys_cdefs_begin_end_decls) {
        $s .= '__BEGIN_DECLS';
        $e .= '__END_DECLS';
    } else {
        $s .= "#ifdef __cplusplus\nextern \"C\" {\n#endif";
        $e .= "#ifdef __cplusplus\n} /* extern \"C\" */\n#endif";
    }
    $substs{BEGIN_DECLS} = $s;
    $substs{END_DECLS}   = $e;
    return;
}

sub process_hashes_conf {
    my ($hashes_conf, $hashes_enabled) = @_;
    my %enabled = enabled_set($hashes_enabled);
    my $hconf   = parse_hashes_conf($hashes_conf);

    my $default_prefix_enabled = 0;
    for my $e (@{$hconf->default_candidates}) {
        if ($enabled{$e->name}) {
            $default_prefix_enabled = 1;
            last;
        }
    }
    $substs{DEFAULT_PREFIX_ENABLED} = $default_prefix_enabled;
    return;
}

sub substitute {
    my ($infile) = @_;
    open my $fh, '<', $infile
        or die "$infile: $!\n";
    my $error = 0;
    local $_;
    my $subst1 = sub {
        my $rep = $substs{$_[0]};
        return $rep if defined $rep;

        print {*STDERR} "$infile:$.: error: no substitution for $_[0]\n";
        $error = 1;
        return "!$_[0]!";
    };
    while (<$fh>) {
        s/@([A-Z0-9_]+)@/&$subst1($1)/eg;
        print;
    }
    exit 1 if $error;
    return;
}

#
# Main
#
if (scalar(@ARGV) != 2 && scalar(@ARGV) != 4) {
    print {*STDERR} 'usage: '
        . ${FindBin::Script}
        . ' template config.h [hashes.conf ,hashes,enabled,]' . "\n";
    exit 1;
}
exit 0 if eval {
    my $infile = shift @ARGV;
    process_config_h(shift @ARGV);
    process_hashes_conf(@ARGV) if @ARGV;
    substitute($infile);
    close STDOUT or die "write error: $!\n";
    1;
};

print {*STDERR} "${FindBin::Script}: $@";
exit 1;
