#!/usr/bin/env perl

use File::Basename;
use File::Compare;
use File::Copy;
use Cwd;

# set sefaults for all options

# the 3 paths below all need to be set if they are not defaults!

# these are INSTALL prefixes, and are not saved in any binaries
$var{prefix}  = "/usr/local";
$var{pdhome}  = "$var{prefix}/lib/pd";

# this is the default location pf will look for it's files if
# the variable PF_HOME is not set
$var{defaultpfhome}  = "$var{prefix}/lib/pf";


$var{debug}   = "no";
$var{pthread} = "no";
$var{profile} = "no";
$var{static}  = "no";
$var{efence} = "no";

$var{silent}   = "yes";
$var{jobs}     = "1";
$var{finkpath} = "/sw";

$var{puredata} = "no";

$var{mmx} = "no";
$var{sdl} = "sdl";

# set defaults for all environment variables
# TODO: these are not saved in reconfigure
$var{cc}        = "cc";
$var{make}      = "make";


# determine dirs. we use absolute paths for convenience
# this means that if you move the project, you need to re-run
# configure, or modify the SRCDIR and BUILDIR variables in Makefile.defs

$var{builddir} = $ENV{BUILDDIR};
$var{srcdir} = $ENV{SRCDIR}; 

$LOGFILE = "$var{builddir}/build.log" ;


# determine platform.
# note that packetforth is not 'portable' by default because of it's unixy nature,
# and it's connection to linux-only input/output devices. adding other platform's
# native io is really straightforward, but not one of my priorities right now.

# i'm no longer using autoconf/automake/libtool soo multiplatform support needs to
# be implemented here.

# set default target name
open UNAME, "uname|";
$target  = <UNAME>;
$target  =~ s/\n//;
close UNAME;

$var{target} = $target;

# default prefix is still /usr/local

#if ("Darwin" eq $target){
#   $var{prefix}="/sw";
#}

# perform a test in the pbuild/tests/ directory. return false on success.
sub troubles {
    my $testname = shift;
    my @args = ($var{make}, "$testname\.test");
    my $retval = system (@args);
    return $retval;
}

sub errorexit {
    print "Test Failed. Check your build environment and/or configure flags.\n" ;
    print "See $LOGFILE for details.\n" ;
    exit(1);
}

# perform a required test
sub check_deps {
    my $thing = shift;
    my $error_msg = shift;
    print "checking $thing";
    my $retval = troubles($thing);
    if ($retval) { print $error_msg; }
    print "\n";
    return $retval;
}

# print valid options

sub print_options {
    foreach $key (keys %var){
	print "\t--$key=$var{$key}\n";
    }
}

# save the current variable state to reconfigure
sub save_reconfigure {
    chdir $var{builddir};
    open STATE, ">./reconfigure";
    print STATE "#/bin/bash\n";
    print STATE "cd `dirname \$0`\n"; # return to the builddir where reconfigure is located
    print STATE "exec $var{srcdir}/configure \\\n";
    foreach $key (keys %var){
	print STATE "\t--$key=$var{$key} \\\n";
    }
    print STATE "\t\$*\n";
    close STATE;
    chmod 0755, "./reconfigure";
}

sub do_help { 
    print "\navailable options with defaults: \n";
    print_options; 
    print "\n--enable|disable-<thing> is equivalent to --<thing>=yes|no\n";
    exit(0); 
}

# override with command line args
while ($a = shift) {
    if ($a eq "--help") { do_help; }
    elsif ($a =~ m/^--enable-(.+)/) { $var{$1} = "yes"; }
    elsif ($a =~ m/^--disable-(.+)/) { $var{$1} = "no"; }
    elsif ($a =~ m/^--(.+?)=(.+)/)  { $var{$1} = $2; }
    else  {print "invalid argument ".$a."\n"; do_help;}
}


# all options parsed: start config



# create config files
chdir $var{builddir};
open PKGCONFIG, ">packetforth.pc";
sub pkgconfig {
    my $line = shift;
    print PKGCONFIG $line . "\n";
}

# FIXME: this isn't correct for debian package
pkgconfig "prefix=$var{prefix}";
pkgconfig "exec_prefix=$var{prefix}";
pkgconfig "libdir=$var{exec_prefix}/lib";
pkgconfig "includedir=$var{exec_prefix}/include";
pkgconfig "";
pkgconfig "Name: packetforth";
pkgconfig "Description: Packet Forth: a language for real-time media processing.";
$VERSION=eval `grep -v ^\# $var{srcdir}/script/version.pf`;
pkgconfig "Version: $VERSION";


open LOCALENV, ">$var{builddir}/vars";
sub localenv {
    my $line = shift;
    print LOCALENV $line . "\n";
}

open MAKEFILE, ">Makefile.defs";
sub makefile {
    my $line = shift;
    print MAKEFILE $line . "\n";
}

# config tools
if ($ENV{CC}){$var{cc} = $ENV{CC};} # get CC, MAKE from environment if defined
if ($ENV{MAKE}){$var{make} = $ENV{MAKE};}



makefile "PREFIX = $var{prefix}" ;
makefile "PDHOME = $var{pdhome}" ;
makefile "SRCDIR = $var{srcdir}" ;
makefile "BUILDDIR = $var{builddir}" ;
makefile "CPPFLAGS += -I$var{srcdir} -I$var{builddir}";
makefile "LOG = $LOGFILE" ;
makefile "SDL_CONFIG = $var{sdl}-config" ;

makefile "CEXT = c";  # default is c. for cc see build/plugins/ftgl/Makefile
makefile "COMPILE = $CC";

if ($var{jobs} > 1){
    makefile "MAKE += -j$var{jobs}"; # run multiple jobs
}


open HEADER, ">pf/config.h.new";
sub header {
    my $line = shift;    print HEADER $line . "\n";
}

header "#ifndef __PF_CONFIG__";
header "#define __PF_CONFIG__";
header "#define PF_HOME \"$var{defaultpfhome}\"";

# start tests and fill config files for base system
chdir "$var{builddir}/tests"; # dir where we will run the tests

# keep track of libs used for base package
# to fill pkgconfig file
@LIBS = (); 

sub check_base {
    push @LIBS, "-lm";
    makefile "LIBS += -lm" ; 
    if (check_deps "base-deps") {
	errorexit;
    }
}


sub check_dl {
    push @LIBS, "-ldl";
    makefile "LIBS += -ldl" ; 
    if (check_deps "libdl") {
	errorexit;
    }
}

sub toolchain {
    makefile "CC = $var{cc}" ;
    makefile "MAKE = $var{make}" ;
    if ($var{silent} eq "yes"){
	makefile "MAKE += -s"; # have a bit less verbose build
    }
}



# add prefix lib & include path 
makefile "LIBS += -L$var{prefix}/lib";
makefile "CPPFLAGS += -I$var{prefix}/include";

# link to pf lib
makefile "LDFLAGS_PF = -L$var{builddir}/libpf -lpf";
makefile "MODEXT = pfo";


# static modules
if ($var{static} eq "yes"){
    header "#define PF_STATIC";               # compile static modules
    makefile "COMPILE_TARGET = echo skipping CC";  # skip target compilation
}
else {
    makefile "COMPILE_TARGET = \$(CC)";    
}
makefile "COMPILE_COMPONENT = \$(CC)";


# check GCC
print "gcc:     ";
$gccver = `$var{cc} -dumpversion`;
chomp $gccver;
if (not $gccver){
    print "C compiler is not gcc.\n";
    exit(1);
}
@gccver = split '\.', $gccver;

if ($gccver[0] < 3){
    print "Need gcc version >= 3\n";
    exit(1);
}
elsif ($gccver[0] == 3){
    header "#define GCC3";
}

print $gccver . "\n";



# todo: include prefix etc
# goal: to compile run in non-root environment for testing
# this means locally some packages need to be installed
# testsuite = pack of libraries.

# platform specific setup
printf "target:  $var{target}\n";
if ("Linux" eq $var{target}){

    # on Darwin, lots of header files are not strict, so i do this only on linux.
    $EXTRA_DEBUG_CFLAGS="-Wstrict-prototypes";  
    $DLIB_PATH="LD_LIBRARY_PATH";
    makefile "DLEXT = so";
    makefile "LDFLAGS_SHARED = -shared";
    makefile "LDFLAGS_MODULE = -shared"; # same on linux
    makefile "OPT_MODULES += v4l rtc SDL";
    toolchain;
    check_base;
    check_dl;
}
elsif ("FreeBSD" eq $var{target}){

    $DLIB_PATH="LD_LIBRARY_PATH";
    $var{make}="gmake";
    makefile "DLEXT = so";
    makefile "LDFLAGS_SHARED = -shared";
    makefile "LDFLAGS_MODULE = -shared"; # same on linux
    toolchain;
    check_base;
}
elsif ("Darwin" eq $var{target}){

    $EXTRA_DEBUG_CFLAGS="-Wno-long-double";
    $DLIB_PATH="DYLD_LIBRARY_PATH";
    makefile "CPPFLAGS += -I$var{finkpath}/include -fpascal-strings";
    makefile "LIBS += -L$var{finkpath}/lib";
    makefile "DLEXT = dylib";
    makefile 'LDFLAGS_SHARED = -dynamiclib -install_name $(PREFIX)/lib/$(TARGET)';
    makefile "LDFLAGS_MODULE = -dynamic -bundle -flat_namespace -undefined suppress" ;
    makefile "CFLAGS += -fPIC -dynamic";
    makefile "OPT_MODULES += carbon";
    push @LIBS, "-L$var{finkpath}/lib";
    toolchain;
    check_base;
    check_dl;
}

# However, if you are building a dll as an export library, 
# you will probably want to use the complete syntax:

# gcc -shared -o cyg${module}.dll \
#    -Wl,--out-implib=lib${module}.dll.a \
#    -Wl,--export-all-symbols \
#    -Wl,--enable-auto-import \
#    -Wl,--whole-archive ${old_libs} \
#    -Wl,--no-whole-archive ${dependency_libs}

# The name of your library is ${module}, prefixed with cyg
# for the DLL and lib for the import library. Cygwin DLLs
# use the cyg prefix to differentiate them from native-Windows
# MinGW DLLs, see the MinGW website for more details. ${old_libs}
# are all your object files, bundled together in static libs or
# single object files and the ${dependency_libs} are import libs
# you need to link against, e.g '-lpng -lz -L/usr/local/special -lmyspeciallib'.

# TODO: use libtool, or add a platform dependent linker script

elsif ("CYGWIN_NT-5.0" eq $var{target}){
    makefile "DLEXT = dll";
    makefile "LDFLAGS_SHARED = -shared";
    makefile "LDFLAGS_MODULE = -shared";
    toolchain;
    check_base;
    #check_dl;  # dlopen is 'just there' on cygwin
}
else {
    printf "Target not supported.\n";
    errorexit();
}


# tools


# create file containing environment variables
localenv "export $DLIB_PATH=$var{builddir}/libpf";
localenv "export PF_HOME=$var{builddir}/lib/pf";
localenv "export PF_SRC=$var{srcdir}";
localenv "export PF=$var{builddir}/tools/pf/pf";
localenv "export PF_CONSOLE=$var{builddir}/tools/console/pf-console";
localenv "export PF_CLIENT=$var{builddir}/tools/client/pf-client";

localenv "export PF_PDOBJ=$var{builddir}/puredata/src/pf";
localenv "export PF_PDEXTRA=$var{srcdir}/puredata/extra";

header "#ifndef HAVE_LIBDL";
header "#define HAVE_LIBDL";
header "#endif";



# DEBUG
if ($var{debug} eq "yes"){
    makefile "CC += $EXTRA_DEBUG_CFLAGS";
    makefile "DEBUG_CFLAGS = -g -Wall -Wno-unused -Wno-parentheses -Werror";
    # perantheses warning is actually quite valueable.. i still make mistakes to if (a = b) vs (a == b) things
    # but most of the code triggers warnings here.. clean up some day.
    header "#define PF_DEBUG 1";  # this is changed to not conflict with pf
}
else{
    makefile "OPTI_CFLAGS = -O3 -ffast-math -funroll-loops";
    if ($var{profile} eq "yes") {
	makefile "OPTI_CFLAGS += -pg -g";
	makefile "LIBS += -pg";
    }
    else {
	makefile "OPTI_CFLAGS += -fomit-frame-pointer";
    }
    header "#define PF_DEBUG 0";
}

# PTHREAD
if ($var{pthread} eq "yes"){
    header "#define HAVE_PTHREAD";
    push @LIBS, "-lpthread";
    makefile "LIBS += -lpthread";
    makefile "OPT_MODULES += console";
    check_deps "libpthread";
}

# MMX
if ($var{mmx} eq "yes"){
    makefile "CFLAGS += -mmmx";
    header "#define HAVE_MMX";
    if (check_deps "mmx", " failed.\nFATAL: Compiler does not support MMX extensions."){
	errorexit; # this is currently fatal..
    }
}




# PUREDATA
if ($var{puredata} eq "yes"){
    if (not check_deps "puredata", " failed. Not building Pure Data module."){
	makefile "OPTIONAL_EXTRA += puredata";
	makefile "OPTIONAL_EXTRA_INSTALL += puredata_install";
    }
}


pkgconfig "Libs: -L$var{prefix}/lib -lpf " . join " ", @LIBS;
pkgconfig "Cflags: -I$var{prefix}/include";


# don't include efence in LIBS or pgkconfig
if ($var{efence} eq "yes"){
    makefile "DEBUG_LIBS += -lefence"; 
}


header "#endif";
close HEADER;
close MAKEFILE;
close PGKCONFIG;
close FAKEPF;
save_reconfigure;


# only update header file if C defs have changed
# this is to speed up rebuilding a bit


if (compare("pf/config.h.new","pf/config.h") == 0) {
    # print "pf/config.h did not change\n";
}
else {
    # print "updating pf/config.h\n";
    copy("pf/config.h.new","pf/config.h");
}

print "Created reconfigure, packetforth.pc, Makefile.defs, fake and pf/config.h\n";
print "Type 'make install' to build and install the package.\n\n";
exit(0);



