#!/usr/bin/perl -w

use strict;

use Carp;
use File::Temp 0.14;
use File::Spec;
use POSIX qw(:errno_h);
use IO::File;

$|=1;

our $Log;
our $LogFile='/dev/null';

my $use_oink;
my $without_idea = !1;

sub trace {
    return if !$Log;

    $Log->seek(0,2);
    print $Log join '',@_;
}

our %Subst=(
	    CRYPTO_LIBS => '-lcrypto',
	    ZLIB => '-lz',
	    BZ2LIB => '-lbz2',
	    CUNITLIB => '-lcunit',
	    INCLUDES => '',
	    CFLAGS => '',
	   );

my %Args=(
	  '--help' => \&usage,
	  '--use-dmalloc' => sub {
	      $Subst{DM_FLAGS}='-I/usr/local/include -DDMALLOC';
	      $Subst{DM_LIB}='/usr/local/lib/libdmalloc.a';
	  },
	  '--with-openssl=' => sub {
	      my $loc=shift;
	      $Subst{INCLUDES}.=" -I $loc/include";
	      $Subst{CRYPTO_LIBS}="$loc/lib/libcrypto.a";
	      # assume developer version if library isn't in .../lib
	      $Subst{CRYPTO_LIBS}="$loc/libcrypto.a"
		  if !-f $Subst{CRYPTO_LIBS};
	  },
	  '--with-zlib=' => sub {
	      my $loc=shift;
	      $Subst{INCLUDES}.=" -I $loc";
	      $Subst{ZLIB}="$loc/libz.a";
	  },
	  '--with-bz2lib=' => sub {
	      my $loc=shift;
	      $Subst{INCLUDES}.=" -I $loc";
	      $Subst{BZ2LIB}="$loc/libbz2.a";
	  },
	  '--with-cunit=' => sub {
	      my $loc=shift;
	      $Subst{INCLUDES}.=" -I $loc/include";
	      $Subst{CUNITLIB}="$loc/lib/libcunit.a";
	  },
	  '--with-otherlibs=' => sub {
	      my $loc=shift;
	      $Subst{OTHERLIBS}="$loc";
	  },
	  '--log=' => sub {
	      $LogFile=shift;
	      $Log=new IO::File(">$LogFile");
	      $Log->autoflush(1);
	  },
	  '--without-idea' => sub {
	      $Subst{CFLAGS}.=' -DOPENSSL_NO_IDEA';
	      $without_idea = 1;
	  },
	  '--64' => sub {
	      $Subst{CFLAGS}.=' -m64';
	      $Subst{LDFLAGS}.=' -m64';
	  },
	  '--maintainer' => sub {
	      $Subst{CFLAGS}.=' -fno-builtin';
	  },
	  '--cc=' => sub {
	      my $cc=shift;
	      $Subst{CC}=$cc;
	  },
	  '--oink=' => sub {
	      $Subst{OINK}=shift;
	  },
	 );

my %Functions=(
	       'socket' => { headers => ['sys/types.h','sys/socket.h'],
			     call => 'socket(0,0,0)',
			     libs => [[],['socket','nsl']] },
	       );

my @Headers=qw(alloca.h);
my @Types=qw(time_t);
my @RHeaders=qw(openssl/bn.h zlib.h bzlib.h CUnit/Basic.h);

our %Knowledge=(
		cc => sub { return $Subst{CC} || chooseBinary('gcc','cc')
			     || croak 'Can\'t find C compiler'; },
		path => sub { return [split /:/,$ENV{PATH}]; },
		time_t => sub { return typeInfo('time_t','time.h'); },
		base_cflags => \&baseCFlags,
		is_gcc => \&isGCC,
		gcc_major => \&gccMajor,
		gcc_version => \&gccVersion,
	      );

while(my $arg=shift) {
    my $arg2;
    if($arg =~ /^(.+=)(.+)$/) {
	$arg=$1;
	$arg2=$2;
    }
    my $code=$Args{$arg};
    croak "Don't understand: $arg" if !defined $code;
    &$code($arg2);
}

if (! $without_idea) {
    push(@RHeaders, "openssl/idea.h");
}

#my $os=`uname -s`;

$Subst{'CC'}=getKnowledge('cc');

$Subst{'CFLAGS'}.=' '.getKnowledge('base_cflags');

checkHeaders(\@RHeaders);

findHeaders(\@Headers);
investigateTypes(\@Types);
my $libs=findLibraries(\%Functions);
$Subst{'LIBS'}=join(' ',map { "-l$_" } @$libs);
checkVersions();
#exit;

if($Subst{OINK}) {
    use Cwd;
    my $path=getcwd();
    $Subst{CC}="$path/util/oink_cc.pl --cc=$Subst{CC}";
}

fixSubst();

my @gpgcmd = ("gpg","--version");
print "Checking for existence of gpg\n";
system (@gpgcmd) == 0
    or die "Cannot find gpg";

create('src/lib/.depend');
create('src/app/.depend');
create('tests/.depend');

fileSubst('src/Makefile','#','');
fileSubst('src/lib/Makefile','#','');
fileSubst('src/app/Makefile','#','');
fileSubst('tests/Makefile','#','');
fileSubst('util/Makefile.oink','#','');
fileSubst('include/openpgpsdk/configure.h','/*','*/');

print "make packet-show-cast.h\n";
system('cd include/openpgpsdk; make packet-show-cast.h') == 0 || exit;
print "make clean\n";
system('make clean') == 0 || exit;
print "make force_depend\n";
if(system('make force_depend') != 0) {
    print STDERR "Configuration failed\n";
    exit 1;
}

sub subst {
    my $line=shift;

    while(my($k,$v)=each %Subst) {
	$line =~ s/\%$k\%/$v/g;
    }
    $line =~ s/\%.+?\%//g;
    return $line;
}

sub chooseBinary {
    my $path=getKnowledge('path');
    while(my $bin=shift) {
	foreach my $p (@$path) {
	    return $bin if -x "$p/$bin";
	}
    }
    return 'false';
}

our $indent;

sub indent {
    for my $n (2..$indent) {
	print "  ";
    }
}

sub showThing {
    my $rthing=shift;

    if(ref $rthing eq 'SCALAR') {
	return $$rthing;
    } elsif(ref $rthing eq 'REF') {
	return showThing($$rthing);
    } elsif(ref $rthing eq 'ARRAY') {
	return join(' ',@$rthing);
    } elsif(ref $rthing eq 'HASH') {
	my $str;
	foreach my $k (keys %$rthing) {
	    $str .= ' ' if $str;
	    $str .= "$k -> $rthing->{$k}";
	}
	return $str;
    }

    print "ref=", ref($rthing), "\n";
    return '?Can\'t display?';
}

sub getKnowledge {
    my $thing=shift;

    croak "Asked for nonexistent knowledge $thing"
      if !exists $Knowledge{$thing};

    ++$indent;
    if(ref $Knowledge{$thing} eq 'CODE') {
	indent();
	print "Finding $thing\n";
	$Knowledge{$thing}=&{$Knowledge{$thing}}();
	indent();
	print "Found $thing: ", showThing(\$Knowledge{$thing}), "\n";
    }
    --$indent;
    return $Knowledge{$thing};
}

sub usage {
    foreach my $k (keys %Args) {
	print "$k\n";
    }
    exit;
}

sub findHeaders {
    my $list=shift;

    foreach my $h (@$list) {
	my $n='HAVE_'.uc $h;
	$n =~ s/[\.\/]/_/;
	print "Looking for header $h ($n)\n";
	if(-e "/usr/include/$h") {
	    $Subst{$n}="#define $n 0";
	} else {
	    $Subst{$n}="#undef $n";
	}
    }
}

sub fileSubst {
    my $file=shift;
    my $cs=shift;
    my $ce=shift;

    open(T,"$file.template") || croak "Can't open $file.template: $!";
    unlink $file;
    open(M,">$file") || croak "Can't create $file: $!";

    print M "$cs generated by configure from $file.template. Don't edit. $ce\n\n";

    while(my $line=<T>) {
	print M subst($line);
    }

    close M;
    close T;

    chmod 0444,"$file";
}

sub fixSubst {
    foreach my $k (keys %Subst) {
	$Subst{$k} =~ s/~/$ENV{HOME}/g;
    }
}

sub create {
    my $file=shift;

    print "Creating $file\n";
    open(D,">$file") || croak "Can't create $file: $!";
    close D;
}

sub build {
    my $code=shift;
    my $link=shift;

    my $cc=getKnowledge('cc');
    my $fh=new File::Temp(SUFFIX => '.c');

    my($v,$d,$f)=File::Spec->splitpath($fh->filename());

    my $cur=File::Spec->rel2abs(File::Spec->curdir());
    chdir($d) || croak "chdir($d): $!";

    print $fh $code;
    $fh->close();

    my $cflag='';
    $cflag='-c' if !defined $link;
    my $cmd="$cc $Subst{CFLAGS} $Subst{INCLUDES} $cflag ".$f;
    $cmd.=" $link" if defined $link;
    trace("$cmd\n--code--\n$code--------\n");
    my $ret=system("$cmd >> $LogFile 2>&1");

    my $obj=$fh->filename();
    $obj =~ s/\.c$/.o/;
    unlink($obj) || $! == ENOENT || croak "unlink($obj): $!";
    unlink('a.out') || $! == ENOENT || croak "unlink(a.out): $!";

    chdir($cur) || croak "chdir($cur): $!";

    return $ret == 0;
}

sub typeInfo {
    my $type=shift;
    my $header=shift;

    my %info;

    print "Getting info about $type.\n";

    foreach my $fmt (qw(%d %ld)) {
	my $code="#include <$header>\n";
	$code .= "#include <stdio.h>\n";
	$code .= "void f(void) { static $type t; printf(\"$fmt\",t); }\n";
	if(build($code)) {
	    $info{fmt}=$fmt;
	    print "  $type printf format is $fmt\n";
	}
    }
    croak "Can't determine print format for $type" if !defined $info{fmt};

    return \%info;
}

sub investigateTypes {
    my $types=shift;

    foreach my $type (@$types) {
	my $info=getKnowledge($type);
	$Subst{uc($type)."_FMT"}="\"$info->{fmt}\"";
    }
}

sub checkHeaders {
    my $headers=shift;

    foreach my $h (@$headers) {
	print "Check required header $h\n";
	build("#include <$h>\n") || croak "Can't find required header $h";
    }
}

sub findLibraries {
    my $funcs=shift;

    my @libs;
  func:
    foreach my $func (keys %$funcs) {
	print "Checking libraries for $func()\n";
	my $code=join("\n",map { "#include <$_>" }
		      @{$funcs->{$func}->{headers}});
	$code.="\nint main() { $funcs->{$func}->{call}; return 0; }\n";
	foreach my $lgroup (@{$funcs->{$func}->{libs}}) {
	    print "  trying ",@$lgroup ? join(', ',@$lgroup) : 'no libraries';
	    if(build($code,join(' ',map { "-l$_" } @$lgroup))) {
		push @libs,@$lgroup;
		print " ... OK\n";
		next func;
	    }
	    print " ... no\n";
	}
    }
    return \@libs;
}

sub baseCFlags {
    my $flags='';
    if(getKnowledge('is_gcc')) {
	$flags='-Wall -Werror -W -g';
#	my $v=getKnowledge('gcc_major');
#	$flags.=' -Wdeclaration-after-statement' if $v >= 3;
    }
    return $flags;
}

sub isGCC {
    my $cc=getKnowledge('cc');

    my $ret=build("int main()\n{\n#ifndef __GNUC__\n  syntax error\n#endif\n return 0; }\n");
    trace("isGCC=$ret\n");
    return $ret;
}

sub gccVersion {
    return undef if !getKnowledge('is_gcc');

    my $cc=getKnowledge('cc');
    my $vstr=`$cc --version`;

    my($v)=$vstr =~ /(\d+\.\d+\.\d+)/;

    trace("gcc version=$v\n");

    return $v;
}

sub gccMajor {
    my $v=getKnowledge('gcc_version');
    return undef if !defined $v;

    ($v)=$v =~ /^(\d+)/;

    trace("gcc major=$v\n");

    return $v;
}

sub checkVersions {
    print "Checking for OpenSSL version\n";
    my $code = << "EOC";
#include "openssl/sha.h"

static SHA256_CTX ctx;
SHA256_CTX *f() { return &ctx; }
EOC
    if(!build($code)) {
	croak "OpenSSL lacks SHA-256 support";
    }
}


	
