#!/usr/bin/env perl

use feature 'say';
use strictures 1;

use List::Objects::WithUtils;

use LWP::UserAgent;
my $ua = LWP::UserAgent->new;

my @headers = (
  'https://raw.githubusercontent.com/zeromq/zeromq3-x/master/include/zmq.h',
  'https://raw.githubusercontent.com/zeromq/zeromq4-x/master/include/zmq.h',
);

my %const;
my %errconst;
my @posixonly = qw/
  EAGAIN
  EFAULT
  EINTR
  EINVAL
/;

# FIXME
#  Pull in the POSIX consts not included in zmq.h, such as EAGAIN and EINTR

# FIXME
#  Better define parser.
#  We should be able to handle ( FOO | BAR ) etc,
#  so we can get ZMQ_EVENT_ALL in particular.

sub parse_consts {
  my ($lines) = @_;
  die "No data?" unless $lines->has_any;
  
  my $defs = $lines->grep(sub { /^#define ZMQ/ });
  my $errordefs = $lines->grep(sub { /^#define E/ });

  die "No defines?" unless $defs->has_any;

  DEF: for my $thisdef ($defs->all) {
    my (undef, $sym, $val) = split /\s+/, $thisdef, 3;
    next DEF if $sym =~ /^ZMQ_VERSION|MAKE_VERSION/;
    if ($val =~ /^\(/) {
      warn "Skipping unhandled sym '$sym' ($val)";
      next DEF
    }
    
    if ($val =~ /[A-Z]/i) {
      if (exists $const{$val}) {
        warn "Aliasing $sym to $val\n";
        $const{$sym} = $const{$val}
      }
    } else {
      $const{$sym} = $val
    }

  }

  ERRORDEF: for my $thisdef ($errordefs->all) {
    my (undef, $sym, $val) = split /\s+/, $thisdef, 3;
    my ($orig, $add) = $val =~ /\((\w+) \+ (\d+)\)/;
    warn "Adding $add to $orig for $sym";
    $errconst{$sym} = $const{$orig} + $add;
    # FIXME these need to be handled differently ..
    #  build a separate %errconst hash and handle it separately
    #  should probably be a compile-time constant def that checks if
    #  POSIX::$const is defined and uses the zmq.h const if not

  }
}


for my $header (@headers) {
  warn "--> fetching $header";
  my $rs = $ua->get($header);
  unless ($rs->is_success) {
    die "Failed retrieval: $header: ".$rs->status_line
  }
  parse_consts( array(split /\n|\r\n/, $rs->decoded_content) )
}

my $output .= <<'HEADER';
package POEx::ZMQ::Constants;
 
# Automatically generated by tools/gen_zmq_constants
use strict; use warnings FATAL => 'all';
require POSIX;
use parent 'Exporter::Tiny';
our @EXPORT = our @EXPORT_ALL = qw/
HEADER

for my $constant (keys %const, keys %errconst, @posixonly) {
  $output .= "  $constant\n"
}
$output .= "/;\n\n";

for my $constant (keys %const) {
  my $val = $const{$constant};
  $output .= "sub $constant () { $val }\n";
}

$output .= "\nno strict 'refs';\n";

for my $constant (keys %errconst) {
  my $val = $errconst{$constant};
  $output .= "eval( defined *{'POSIX::$constant'} ?\n";
  $output .= "  'sub $constant () { POSIX::$constant }'\n";
  $output .="   : 'sub $constant () { $val }'\n);\n\n";
}

for my $constant (@posixonly) {
  $output .= "sub $constant () { \n";
  $output .= "  POSIX::$constant\n";
  $output .= "}\n";
}


$output .= "\n1;\n";
$output .= " # Generated at " . localtime . "\n";

$output .= <<'PODMAIN';

=pod

=head1 NAME

POEx::ZMQ::Constants - ZeroMQ (3 + 4) constants for use with POEx::ZMQ

=head1 SYNOPSIS

  # All ZeroMQ v3 + v4 constants:
  use POEx::ZMQ::Constants -all;
  # Specific constants:
  use POEx::ZMQ::Constants qw/ZMQ_ROUTER ZMQ_DEALER EINTR/;

=head1 DESCRIPTION

ZeroMQ constant exporter for use with L<POEx::ZMQ> applications.

Automatically generated from ZeroMQ version 3 & 4 headers.

Uses L<Exporter::Tiny>; look there for detailed import-related documentation.

C<E>-prefixed error constants should generally do the right thing, using the
ZeroMQ C<zmq.h> values if the POSIX constants are not available.

The complete list of exported constants:

=over

PODMAIN

$output .= "=item $_\n\n" for (keys %const, keys %errconst, @posixonly);

$output .= <<'PODFOOTER';
=back

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

=cut

PODFOOTER

print $output;

