#!/usr/bin/env perl

use File::Basename;

$utildirname = dirname($0);

$COMPILE = undef;
@compilelines = ();
#print STDERR "Running $utildirname/compileline --compile\n";
open(CCMD, "$utildirname/compileline --compile |") or die "can't open compileline: $!\n";
while(<CCMD>) {
  chomp;
  if( not defined $COMPILE ) {
    $COMPILE = $_;
  }
}
close(CCMD);

$CALC = "bc -q";

$test_bc_result = `echo "1+2" | $CALC 2>&1`;
chomp($test_bc_result);
if ( $test_bc_result != "3" ) {
    die("Failed to get a result from bc. Is bc installed and available on the PATH?\nError message: $test_bc_result");
}

$HTMP = "find_int_sizes_tmp.h";
$CALCTMP = "find_int_sizes_tmp.txt";

$cpreprocess = <<END;
#include "sys_basic.h"
#include <limits.h>
#include <stdint.h>
#include <math.h>

FIND_INT_SIZES_START
END

@max_to_type = (
"INT_MAX", "c_int",
"UINT_MAX", "c_uint",
"LONG_MAX", "c_long",
"ULONG_MAX", "c_ulong",
"LLONG_MAX", "c_longlong",
"ULLONG_MAX", "c_ulonglong",
"CHAR_MAX", "c_char",
"SCHAR_MAX", "c_schar",
"UCHAR_MAX", "c_uchar",
"SHRT_MAX", "c_short",
"USHRT_MAX", "c_ushort",
#"UINTPTR_MAX" => "c_void_ptr", # comment out if you want pointer opaque so no ptr arithmetic
"SSIZE_MAX", "ssize_t",
"SIZE_MAX", "size_t",
);

@maxes = ( );

for (my $i = 0; $i < @max_to_type; $i+=2 ) {
  my $m = $max_to_type[$i];
  $cpreprocess .= $m . "\n";
  push(@maxes, $m);
}


open(CFILE, "> $HTMP") || die ("can't open cfile $HTMP: $!");

print CFILE $cpreprocess;

close(CFILE);

@values = ( );
$keep = 0;

#print STDERR "Running $COMPILE -E $HTMP\n";
open(PREPROC, "$COMPILE -E $HTMP |") or die("can't open $COMPILE -E $HTMP");
while(<PREPROC>) {
  chomp;
  if( /^\#/ ) {
    # ignore #line
  } elsif( /^\/\// ) {
    # ignore comment
  } elsif( /FIND_INT_SIZES_START/ ) {
    $keep = 1;
  } elsif( $keep ) {
    push(@values, $_);
  }
}
close(PREPROC);

int(@values) == int(@maxes) or die "Got wrong number of values from preprocessor";

open(CALCFILE, "> $CALCTMP") || die ("can't open calcfile $CALCTMP: $!");

# The default base for bc; we need to track because once you've switched
# to base 16, you have to use "A" to switch back to base 10... :P
#
$base = 10;   

for my $v (@values) {
#    print "Raw value is $v\n";

  # Throw away any non-numeric u or l after a digit.
  $v =~ s/[uUlL]//g;

# check for hex values
  if ($v =~ /0x/) {
      $v =~ s/0x//g;

      # switch to uppercase, which bc requires
      $v = uc($v);

      # if we're not already in base 16, switch to it
      if ($base == 10) {
	  print CALCFILE "ibase=16\n";
      }
      $base = 16;
  } else {
      # if we're not in base 10, switch back to it (must use A; 10 is 17)
      if ($base = 16) {
	  print CALCFILE "ibase=A\n";
      }
      $base = 10;
  }
#  print "Feeding $v into CALCFILE\n";
  print CALCFILE $v . "\n";
}

close(CALCFILE);

@values = ( );

#print STDERR "Running $CALC < $CALCTMP\n";
open(CALCF, "$CALC < $CALCTMP |");
while(<CALCF>) {
  chomp;
  if( /^\d/ ) {
    push(@values, $_);
  }
}
close(CALCF);

int(@values) == int(@maxes) or die "Got wrong number of values from calculator";

sub maxToType {
  my $m = shift;
  if ($m eq "127") { return "int(8)"; }
  if ($m eq "255") { return "uint(8)"; }
  if ($m eq "32767") { return "int(16)"; }
  if ($m eq "65535") { return "uint(16)"; }
  if ($m eq "255") { return "uint(8)"; }
  if ($m eq "4294967295") { return "uint(32)"; }
  if ($m eq "2147483647") { return "int(32)"; }
  if ($m eq "4294967295") { return "uint(32)"; }
  if ($m eq "9223372036854775807") { return "int(64)"; }
  if ($m eq "18446744073709551615") { return "uint(64)"; }
  die "Unknown numeric limit $m in maxToType";
}

@rhstypes = ( );

$handled_c_ptr = 0;

for (my $i = 0; $i < @values; $i++ ) {
  my $v = $values[$i];
  my $max = $maxes[$i];
  my $othermax = $max_to_type[2*$i];
  $max eq $max_to_type[2*$i] or die "max type mismatch";
  my $typelhs = $max_to_type[2*$i+1];
  my $typerhs = maxToType($v);
  push(@rhstypes, $typerhs);
  print "extern type $typelhs = $typerhs;\n";
  if( $typelhs eq "c_ptr" ) {
    $handled_c_ptr = 1;
  }
}

if( not $handled_c_ptr ) {
  print "extern type c_void_ptr; // opaque; no ptr arithmetic in Chapel code!\n";
}

int(@values) == int(@rhstypes) or die "Got wrong number of type aliases";

print "\n";
print "{\n";
print "  pragma \"no prototype\"\n";
print "  extern proc sizeof(type t): size_t;\n";
print "\n";
for (my $i = 0; $i < @values; $i++ ) {
    my $typelhs = $max_to_type[2*$i+1];
    my $typerhs = $rhstypes[$i];
    print "  assert(sizeof($typelhs) == sizeof($typerhs));\n";
}
print "}\n";

unlink($HTMP) or die "Could not delete $HTMP:$!";
unlink($CALCTMP) or die "Could not delete $CALCTMP:$!";


