use 5.008;

use strict;
use warnings;

use ExtUtils::MakeMaker;
use ExtUtils::Constant qw(WriteConstants);
use Config;

my $libdirs = $ENV{JS_LIBS} || '';
my $incdirs = $ENV{JS_INC} || '';
my $defines = $ENV{JS_DEFINES} || '';
my $CC = $Config{cc};
my $myextlib = '';
my $needtest = 1;

if($^O eq 'MSWin32') {
    # Search in PATH for xulrunner SDK
    my $xulsdk = $ENV{XUL_PATH};
    unless($xulsdk) {
	my @dirs = split ';', $ENV{PATH};
	for(@dirs) {
	    if(-x "$_/xulrunner.exe" && -e "$_/../include/jsapi.h") {
		$xulsdk = $_;
		last;
	    }
	}
	unless($xulsdk) {
	    warn "Can't found your XulRunner SDK directory in PATH\n";
	    exit 0;
	}
	$xulsdk =~ s/bin$//;
    }
    warn "XulRunner SDK found in '$xulsdk'\n";

    my($dll) = glob "$xulsdk\\bin\\js*.dll";
    my($libname) = $dll =~ /(js\d+)\.dll/;
    $libdirs = "-L${xulsdk}lib -l$libname -lnspr4";
    $incdirs = "-I${xulsdk}include";
    $defines = "-DXP_WIN=1 -D_CRT_SECURE_NO_WARNINGS -TP";
    my $mozver;
    {
	open(my $fd, "<${xulsdk}include/mozilla-config.h");
	while(my $line = <$fd>) {
	    chomp $line;
	    if($line =~ /#define MOZILLA_VERSION "(.+)"/) {
		$mozver = $1; last
	    }
	}
    }
    die "Can't found my MOZILLA_VERSION\n" if(!$mozver);
    open(my $fd, ">JS_Env.h") or die "Can't create JS_Env.h: $!\n";
    print $fd <<EOF;
/* Autogenerated, don't edit */
#define JS_THREADSAFE	1
EOF

    warn "Will use\n\tJS_LIBS=$libdirs\n\tJS_INC=$incdirs\n\tJS_DEFINES=$defines\n";
    $needtest=0;
}

if((my $src = $ENV{JS_SRC}) && $CC eq 'gcc') {
    # For developers only.
    # SM is uninstalled, so uses the static library.
    $incdirs = "-I$ENV{JS_SRC} -I$ENV{JS_SRC}/..";
    if(-x "$src/js-config") { # A recent SM
	my $cflags = qx($src/js-config --cflags);
	chomp $cflags;
	my @incs = split ' ', $cflags;
	$incdirs .= ' ' . $incs[1] if @incs > 1;
	
	$libdirs = qx($src/js-config --libs);
	chomp $libdirs;
	my @libs = split(' ', $libdirs);
	push @libs, '-lpthread' if @incs == 1; # HACK for broken SM from hg
	$libdirs = join(' ', @libs[2 .. $#libs]); 
    } else {
	$libdirs = "-lm -lnspr4";
    }
    $defines .= ' -Wno-unused-variable -DXP_UNIX';
    ($myextlib) = glob "$ENV{JS_SRC}/libjs*.a";

    die "Your SpiderMonkey doesn't seems to be compiled yet.\n"
	unless(-f $myextlib);

    my($vhead) = grep -f $_, glob "$ENV{JS_SRC}/../js{version,config}.h";
    my $ver = (split ' ',`grep "#define JS_VERSION " $vhead`)[2];
    if($ver >= 185) {
	$CC = 'c++';
    }

    warn "Found SpiderMonkey $ver (for static build)\n";
    warn "Will use\n\tJS_LIBS=$libdirs\n\tJS_INC=$incdirs\n\tJS_DEFINES=$defines\n\tEXTLIB=$myextlib\n";
}

my $pkgn = '';
unless($libdirs && $incdirs) {
PKGCONF: {
    # In the common case we depend on a pkg-config file.
    my @build_reqs = (
	'mozjs185',
	'libjs',
	'mozilla-js',
    );
    unshift @build_reqs, $ARGV[0] if $ARGV[0];

    for(@build_reqs) {
	#warn "Testing for $_...\n";
	my $res = system 'pkg-config', '--exists',  /\d$/ ? $_ : "$_ >= 1.7";
	if($res == -1) {
	    warn "Can't run pkg-config\n";
	    last PKGCONF;
	}
	if(!$res) {
	    $pkgn = $_;
	    last;
	}
    }

    unless($pkgn) {
	warn "Sad, I can't find any pkg-config file for SpiderMonkey\n";
	last;
    }
    my $vers = `pkg-config --modversion $pkgn`;
    chomp $vers;
    $vers = '1.8.5' if !$vers && $pkgn eq 'mozjs185'; # Missing 'Version' field?
    warn "Found $pkgn ($vers)\n";
    $libdirs = `pkg-config --libs $pkgn`;
    chomp $libdirs;
    $defines = `pkg-config --cflags-only-other $pkgn`;
    chomp $defines;
    $incdirs = `pkg-config --cflags-only-I $pkgn`;
    chomp $incdirs;

    $defines .= ' -DXP_UNIX' unless $defines =~ /-DXP/; # Just in case.

    if($incdirs =~ m{^-I/usr/local/include/libxul/stable -I/usr/local/include/nspr}) {
	# Fix for broken pkg-config in freebsd
	$incdirs .= "-I/usr/local/include/libxul/js";
    }

    {
	no warnings;
	if(($pkgn =~ /^mozilla-js/ && int($vers) >= 2) ||
	   ($pkgn eq 'libjs' && $vers gt "1.8.0") ||
           ($pkgn eq 'mozjs185')
        ) {
	    $CC = 'c++'
	}
    }

    warn qq{Will use\n\tJS_LIBS="$libdirs"\n\tJS_INC="$incdirs"\n\tJS_DEFINES="$defines"\n};
}}

unless($incdirs && $libdirs) {
BRUTE: {
    warn "Will try a brute-force search, review the results...\n";
    my @lpaths = qw(/usr/lib64 /usr/lib /usr/local/lib64 /usr/local/lib /opt/local/lib);
    my @ipaths = qw(
	/usr/include/mozjs /usr/include/smjs /usr/include/js
        /usr/local/include/mozjs /usr/local/include/smjs /usr/local/include/js
        /usr/include /usr/local/include /opt/local/include/js
    );
    my(@incs, @libs, $lib);
    unless($incdirs) {
	my $hash=0;
	for my $p (@ipaths) {
	    if(-f("$p/jsapi.js") && -f("$p/jsdbgapi.h")) {
		push @incs, "-I$p";
		$hash++;
	    }
	    if(-f "$p/nspr/pratom.h") {
		push @incs, "-I$p/nspr";
	    } elsif(-f "$p/nspr4/pratom.h") {
		push @incs, "-I$p/nspr4";
	    }
	}
	if($hash != 1) {
	    warn "Sad, no SM headers found\n";
	    last BRUTE;
	}
	$incdirs = join(' ', @incs);
    }
    ALL:
    for my $path (@lpaths) {
	if(!$lib) {
	    SEARCHLIB:
	    for my $l ( qw(mozjs smjs js) ) {
		if(-f "$path/lib$l.so") {
		    push @libs, "-L$path -l$l";
		    $lib = $l;
		    last SEARCHLIB;
		}
	    }
	}
        if(-f "$path/libnspr.so") {
	    push @libs, "-L$path -lnspr";
	} elsif( -f "$path/libnspr4.so") {
	    push @libs, "-L$path -lnspr4";
	}
    }
    if(!$lib) {
	warn "Sad, no SM library found\n";
	last BRUTE;
    }
    $libdirs = join(' ', @libs);
    $defines = "-DXP_UNIX";

    warn qq{Will use\n\tJS_LIBS="$libdirs"\n\tJS_INC="$incdirs"\n\tJS_DEFINES="$defines"\n};
}}

unless($incdirs && $libdirs) {
    warn <<EOF;
SpiderMonkey headers and libraries not found.

Try manually defining JS_LIBS, JS_INC and JS_DEFINES environment variables like:
export JS_LIBS="-L/path/to/spidermonkey/library -l<libname>"
export JS_INC="-I/path/to/spidermonkey/include"
export JS_DEFINE="-DXP_UNIX"
EOF
    exit 0;
}

# Compile a test to determine if we can find libs and headers and
# to generate the dynamic header file
if($needtest) {
    require File::Temp;
    my $exe = "utils/testjs"; #File::Temp::tmpnam();
    unlink $exe;
    my $ldrp = (ExtUtils::Liblist->ext($libdirs, 0, 0))[3];
    $ENV{LD_RUN_PATH} = $ldrp if $ldrp;
    my $cc = join(" ", $CC, $defines, $incdirs, '-g -o', $exe);
    warn "Testing the environment...\n";
    #warn "$cc testjs.c $myextlib\n";
    qx($cc utils/testjs.c $libdirs $myextlib);
    if($?) {
	warn "Test compile failed, check JS_INC, JS_LIBS and JS_DEFINE for errors.\n";
	warn "Your package-config file '$pkgn.pc' can be broken!\n" if $pkgn;
	exit 1; # I want smokers reports
    }

    my $res = qx($exe);
    if(!$res) {
	warn "Unusable library, please make sure it's available in your LD_LIBRARY_PATH.\n";
	exit 1; # I want smokers reports
    }
    open(my $fd, ">JS_Env.h") or die "Can't create JS_Env.h: $!\n";
    print $fd "/* Autogenerated, don't edit */\n$res\n";
    unlink($exe);
}

my $NAME = 'JSPL';

# Write makefile
WriteMakefile(
    NAME            => $NAME,
    VERSION_FROM    => "lib/$NAME.pm",
    PREREQ_PM       => {
        "Test::More"      => 0,
        "Test::Exception" => 0,
    },
    ABSTRACT_FROM   => "lib/$NAME.pm", # retrieve abstract from module
    AUTHOR          => "Salvador Ortiz <sortiz\@cpan.org>",
    DEFINE          => "$defines",
    LIBS            => "$libdirs",
    INC             => $incdirs,
    (
	eval { ExtUtils::MakeMaker->VERSION(6.3001) } ? (LICENSE => "perl") : () 
    ),
    EXE_FILES	    => [ 'bin/jspl' ],
    OBJECT          => q/$(O_FILES)/,
    CC              => $CC,
    DL_FUNCS	    => {
	$NAME => [], $NAME."::RawObj" => [], $NAME."::Script" => [],
	$NAME."::PerlClass" => [], $NAME."::TrapHandler" => [],
	$NAME."::Context::Timeout" => [], $NAME."::SM::Opcode" => [],
    },
    MYEXTLIB        => $myextlib,
    (
	$CC eq 'c++' ? (XSOPT => '-C++', LD => $CC) : ()
    ),
    clean => { FILES => [ 'JS_Env.h', 'const-c.inc', 'const-xs.inc',
	                  'op-const-c.inc', 'op-const-xs.inc'] },
);

WriteConstants(
    NAME => $NAME,
    SUBNAME => '_constant',
    XS_SUBNAME => '_constant',
    NAMES => [
	map("JSOPTION_$_", qw(STRICT VAROBJFIX XML JIT ANONFUNFIX)),
    ]
);

WriteConstants(
    NAME => $NAME.'::SM::Opcode', 
    SUBNAME => '_constant',
    XS_SUBNAME => '_constant',
    C_FILE => 'op-const-c.inc',
    XS_FILE => 'op-const-xs.inc',
    NAMES => [ map("JOF_$_", qw(
	BYTE JUMP ATOM UINT16 TABLESWITCH LOOKUPSWITCH QARG LOCAL SLOTATOM JUMPX
	TABLESWITCHX LOOKUPSWITCHX UINT24 UINT8 INT32 OBJECT SLOTOBJECT REGEX INT8
	ATOMOBJECT UINT16PAIR TYPEMASK NAME PROP ELEM XMLNAME VARPROP MODEMASK SET
	DEL DEC INC INCDEC POST FOR ASSIGNING DETECTING BACKPATH LEFTASSOC DECLARING
	INDEXBASE CALLOP PARENTHEAD INVOKE TMPSLOT TMPSLOT2 TMPSLOT_SHIFT TMPSLOT_MASK
	SHARPSLOT
    ))]
);

package MY;

use File::Spec;

sub post_initialize {
    my($self) = shift;

    my @headers = <*.h>;
    for (@headers, 'typemap' ) {
        $self->{PM}->{$_} = File::Spec->catfile($self->{INST_ARCHAUTODIR}, $_);
    }
    return '';
}