#!perl -w
use strict;
use ExtUtils::MakeMaker;
use Cwd;
use Config;
use File::Spec;
use Getopt::Long;
use ExtUtils::Manifest qw(maniread);
use ExtUtils::Liblist;
use vars qw(%formats $VERBOSE $INCPATH $LIBPATH $NOLOG $DEBUG_MALLOC $MANUAL $CFLAGS $LFLAGS $DFLAGS);
use lib 'inc', 'lib';
use Imager::Probe;

# EU::MM runs Makefile.PL all in the same process, so sub-modules will
# see this
our $BUILDING_IMAGER = 1;

# used to display a summary after we've probed the world
our %IMAGER_LIBS;

#
# IM_INCPATH      colon seperated list of paths to extra include paths
# IM_LIBPATH      colon seperated list of paths to extra library paths
#
# IM_VERBOSE      turns on verbose mode for the library finding and such
# IM_MANUAL       to manually select which libraries are used and which not
# IM_ENABLE       to programmatically select which libraries are used
#                 and which are not
# IM_NOLOG        if true logging will not be compiled into the module
# IM_DEBUG_MALLOC if true malloc debbuging will be compiled into the module
#                 do not use IM_DEBUG_MALLOC in production - this slows
#                 everything down by alot
# IM_CFLAGS       Extra flags to pass to the compiler
# IM_LFLAGS       Extra flags to pass to the linker
# IM_DFLAGS       Extra flags to pass to the preprocessor

my $KEEP_FILES = $ENV{IMAGER_KEEP_FILES};

# make sure --verbose will dump environment settings
if (grep $_ =~ /^--?v(?:erbose)?$/, @ARGV) {
  $VERBOSE = 1;
}

# modules/features bundled with Imager that can be enabled/disabled
# withs --enable/--disable
my @bundled = qw(FT1 FT2 GIF JPEG PNG T1 TIFF W32);

# extra modules bundled with Imager not available on CPAN
my @extras = qw(CountColor DynTest ICO SGI Mandelbrot Flines);

# alternate names for modules
my %bundled_names = qw(win32 w32 tt ft1);

getenv();     # get environment variables

my $lext=$Config{'so'};   # Get extensions of libraries
my $aext=$Config{'_a'};

my $help; # display help if set
my @enable; # list of drivers to enable
my @disable; # or list of drivers to disable
my @incpaths; # places to look for headers
my @libpaths; # places to look for libraries
my $coverage; # build for coverage testing
my $assert; # build with assertions
my $trace_context; # trace context management to stderr
GetOptions("help" => \$help,
           "enable=s" => \@enable,
           "disable=s" => \@disable,
           "incpath=s", \@incpaths,
           "libpath=s" => \@libpaths,
           "verbose|v" => \$VERBOSE,
           "nolog" => \$NOLOG,
	   'coverage' => \$coverage,
	   "assert|a" => \$assert,
	   "tracecontext" => \$trace_context);

setenv();

if ($ENV{AUTOMATED_TESTING}) {
  $assert = 1;
}

if ($VERBOSE) { 
  print "Verbose mode\n"; 
  require Data::Dumper; 
  import Data::Dumper qw(Dumper);
}

if ($help) {
  usage();
}

my @defines;

if ($NOLOG)   { print "Logging not compiled into module\n"; }
else { 
  push @defines, [ IMAGER_LOG => 1, "Logging system" ];
}

if ($assert) {
  push @defines, [ IM_ASSERT => 1, "im_assert() are effective" ];
}

if ($DEBUG_MALLOC) {
  push @defines, [ IMAGER_DEBUG_MALLOC => 1, "Use Imager's DEBUG malloc()" ];
  print "Malloc debugging enabled\n";
}

if (@enable && @disable) {
  print STDERR "Only --enable or --disable can be used, not both, try --help\n";
  exit 1;
}

my %definc;
my %deflib;
my @incs; # all the places to look for headers
my @libs; # all the places to look for libraries

init();       # initialize global data
pathcheck();  # Check if directories exist

my @enabled_bundled;
if (exists $ENV{IM_ENABLE}) {
  push @enable, split ' ', $ENV{IM_ENABLE};
}
if (@enable) {
  my %en = map { lc $_ => 1 } map_bundled(@enable);
  @enabled_bundled = grep $en{lc $_}, @bundled;
}
elsif (@disable) {
  my %dis = map { lc $_ => 1 } map_bundled(@disable);
  @enabled_bundled = grep !$dis{lc $_}, @bundled;
}
else {
  @enabled_bundled = @bundled;
}

# Pick what libraries are used
if ($MANUAL) {
  manual();
} else {
  automatic();
}

my @objs = qw(Imager.o context.o draw.o polygon.o image.o io.o iolayer.o
              log.o gaussian.o conv.o pnm.o raw.o feat.o combine.o
              filters.o dynaload.o stackmach.o datatypes.o
              regmach.o trans2.o quant.o error.o convert.o
              map.o tags.o palimg.o maskimg.o img8.o img16.o rotate.o
              bmp.o tga.o color.o fills.o imgdouble.o limits.o hlines.o
              imext.o scale.o rubthru.o render.o paste.o compose.o flip.o
	      perlio.o imexif.o trim.o);

my $lib_define = '';
my $lib_inc = '';
my $lib_libs = '';
for my $frmkey (sort { $formats{$a}{order} <=> $formats{$b}{order} } keys %formats) {
  my $frm = $formats{$frmkey};
  if ($frm->{enabled}) {
    push @defines, [ $frm->{def}, 1, "$frmkey available" ];
    push @objs, $frm->{objfiles};
    $lib_define .= " $frm->{DEFINE}" if $frm->{DEFINE};
    $lib_inc    .= " $frm->{INC}"    if $frm->{INC};
    $lib_libs   .= " $frm->{LIBS}"   if $frm->{LIBS};
  }
}

my $OSLIBS = '';
my $OSDEF  = "-DOS_$^O";

if ($^O eq 'hpux')                { $OSLIBS .= ' -ldld'; }
if (defined $Config{'d_dlsymun'}) { $OSDEF  .= ' -DDLSYMUN'; }

if ($Config{useithreads}) {
  if ($Config{i_pthread}) {
    print "POSIX threads\n";
    push @objs, "mutexpthr.o";
  }
  elsif ($^O eq 'MSWin32') {
    print "Win32 threads\n";
    push @objs, "mutexwin.o";
  }
  else {
    print "Unsupported threading model\n";
    push @objs, "mutexnull.o";
    if ($ENV{AUTOMATED_TESTING}) {
      die "OS unsupported: no threading support code for this platform\n";
    }
  }
}
else {
  print "No threads\n";
  push @objs, "mutexnull.o";
}

my @typemaps = qw(typemap.local typemap);
if ($] < 5.008) {
    unshift @typemaps, "typemap.oldperl";
}

if ($trace_context) {
  $CFLAGS .= " -DIMAGER_TRACE_CONTEXT";
}

my $tests = 't/*.t t/*/*.t';
if (-d "xt" && scalar(() = glob("xt/*.t"))) {
  $tests .= " xt/*.t";
}

my %opts=
  (
   NAME         => 'Imager',
   VERSION_FROM => 'Imager.pm',
   LIBS         => "$LFLAGS -lm $lib_libs $OSLIBS",
   DEFINE       => "$OSDEF $lib_define $CFLAGS",
   INC          => "$lib_inc $DFLAGS",
   OBJECT       => join(' ', @objs),
   DIR          => [ sort grep -d, @enabled_bundled, @extras ],
   clean          => { FILES=>'testout rubthru.c scale.c conv.c  filters.c gaussian.c render.c rubthru.c cov-int' },
   PM             => gen_PM(),
   PREREQ_PM      =>
   { 
    'Test::More' => 0.99,
    'Scalar::Util' => 1.00,
    'XSLoader'    => 0,
    'Exporter'    => '5.57',
   },
   TYPEMAPS       => \@typemaps,
   test =>        { TESTS => $tests },
  );

if ($coverage) {
    if ($Config{gccversion}) {
	push @ARGV, 'OPTIMIZE=-ftest-coverage -fprofile-arcs -g';
	$opts{dynamic_lib} = { OTHERLDFLAGS => '-ftest-coverage -fprofile-arcs' };
    }
    else {
	die "Don't know the coverage C flags for your compiler\n";
    }
}

if (eval { ExtUtils::MakeMaker->VERSION('6.06'); 1 }) {
  $opts{AUTHOR} = 'Tony Cook <tonyc@cpan.org>, Arnar M. Hrafnkelsson';
  $opts{ABSTRACT} = 'Perl extension for Generating 24 bit Images';
}

if (eval { ExtUtils::MakeMaker->VERSION('6.46'); 1 }) {
  $opts{LICENSE} = "perl_5";
  $opts{META_MERGE} =
    {
     'meta-spec' =>
     {
      version => "2",
      url => "https://metacpan.org/pod/CPAN::Meta::Spec",
     },
     prereqs =>
     {
      runtime =>
      {
       recommends =>
       {
        "Parse::RecDescent" => 0
       },
       requires =>
       {
	'Scalar::Util' => "1.00",
	XSLoader => 0,
	'Exporter'    => '5.57',
       },
      },
      build =>
      {
       requires =>
       {
	XSLoader => 0,
	'Exporter' => '5.57',
       },
      },
      test =>
      {
       requires =>
       {
	'Test::More' => "0.99",
       },
      },
     },
     dynamic_config => 0,
     no_index =>
     {
      directory =>
      [
       "PNG",
       "GIF",
       "TIFF",
       "JPEG",
       "W32",
       "FT2",
       "T1",
      ],
     },
     resources =>
     {
      homepage => "http://imager.perl.org/",
      repository =>
      {
       url => "git://github.com/tonycoz/imager.git",
       web => "https://github.com/tonycoz/imager.git",
       type => "git",
      },
      bugtracker =>
      {
       web => "https://github.com/tonycoz/imager/issues",
      },
     },
    };
}

make_imconfig(\@defines);

if ($VERBOSE) { print Dumper(\%opts); }
mkdir('testout',0777); # since we cannot include it in the archive.

-d "probe" and rmdir "probe";

WriteMakefile(%opts);

my @good;
my @bad;
for my $name (sort { lc $a cmp lc $b } keys %IMAGER_LIBS) {
  if ($IMAGER_LIBS{$name}) {
    push @good, $name;
  }
  else {
    push @bad, $name;
  }
}

print "\n";
print "Libraries found:\n" if @good;
print "  $_\n" for @good;
print "Libraries *not* found:\n" if @bad;
print "  $_\n" for @bad;

exit;


sub MY::postamble {
    my $self = shift;
    my $perl = $self->{PERLRUN} ? '$(PERLRUN)' : '$(PERL)';
    my $mani = maniread;

    my @ims = grep /\.im$/, keys %$mani;
'
dyntest.$(MYEXTLIB) : dynfilt/Makefile
	cd dynfilt && $(MAKE) $(PASTHRU)

lib/Imager/Regops.pm : regmach.h regops.perl
	$(PERL) regops.perl regmach.h lib/Imager/Regops.pm

imconfig.h : Makefile.PL
	$(ECHO) "imconfig.h out-of-date with respect to $?"
	$(PERLRUN) Makefile.PL
	$(ECHO) "==> Your Makefile has been rebuilt - re-run your make command <=="
'.qq!
lib/Imager/APIRef.pod : \$(C_FILES) \$(H_FILES) apidocs.perl
	$perl apidocs.perl lib/Imager/APIRef.pod

!.join('', map _im_rule($perl, $_), @ims)

}

sub _im_rule {
  my ($perl, $im) = @_;

  (my $c = $im) =~ s/\.im$/.c/;
  return <<MAKE;

$c: $im lib/Imager/Preprocess.pm
	$perl -Ilib -MImager::Preprocess -epreprocess $im $c

MAKE

}

# manual configuration of helper libraries

sub manual {
  print <<EOF;

      Please answer the following questions about
      which formats are avaliable on your computer

      Warning: if you use manual configuration you are responsible for
      configuring extra include/library directories as necessary using
      INC and LIBS command-line assignments.

press <return> to continue
EOF

  <STDIN>; # eat one return

  for my $frm(sort { $formats{$b}{order} <=> $formats{$a}{order} } keys %formats) {
  SWX:
    if ($formats{$frm}{docs}) { print "\n",$formats{$frm}{docs},"\n\n"; }
    print "Enable $frm support: ";
    my $gz = <STDIN>;
    chomp($gz);
    if ($gz =~ m/^(y|yes|n|no)/i) {
      if ($gz =~ /y/i) {
	$formats{$frm}{enabled} = 1;
	$IMAGER_LIBS{$frm} = 1;
      }
    } else { goto SWX; }
  }
}


# automatic configuration of helper libraries

sub automatic {
  print "Automatic probing:\n" if $VERBOSE;

  if (grep $_ eq "FT1", @enabled_bundled) {
    my %probe =
      (
       name => "FT1",
       inccheck => sub { -e File::Spec->catfile($_[0], "ftnameid.h") },
       libbase => "ttf",
       testcode => _ft1_test_code(),
       testcodeheaders => [ "freetype.h", "stdio.h" ],
       incpaths => \@incpaths,
       libpaths => \@libpaths,
       alternatives =>
       [
	{
	 incsuffix => "freetype",
	}
       ],
       verbose => $VERBOSE,
      );
    my $probe_res = Imager::Probe->probe(\%probe);
    $IMAGER_LIBS{FT1} = defined $probe_res;
    if ($probe_res) {
      $formats{FT1}{enabled} = 1;
      @{$formats{FT1}}{qw/DEFINE INC LIBS/} =
	@$probe_res{qw/DEFINE INC LIBS/};
    }
  }
}

sub pathcheck {
  if ($VERBOSE) {
    print "pathcheck\n";
    print "  Include paths:\n";
    for (@incs) { print $_,"\n"; }
  }
  @incs=grep { -d $_ && -r _ && -x _ or ( print("  $_ doesnt exist or is unaccessible - removed.\n"),0) } @incs;

  if ($VERBOSE) {
    print "\nLibrary paths:\n";
    for (@libs) { print $_,"\n"; }
  }
  @libs=grep { -d $_ && -r _ && -x _ or ( print("  $_ doesnt exist or is unaccessible - removed.\n"),0) } @libs;
  print "\ndone.\n";
}


# Format data initialization

# format definition is:
# defines needed
# default include path
# files needed for include (boolean perl code)
# default lib path
# libs needed
# files needed for link (boolean perl code)
# object files needed for the format


sub init {

  my @definc = qw(/usr/include);
  @definc{@definc}=(1) x @definc;
  @incs=
    (
     split(/\Q$Config{path_sep}/, $INCPATH),
     map _tilde_expand($_), map { split /\Q$Config{path_sep}/ } @incpaths 
    );
  if ($Config{locincpth}) {
    push @incs, grep -d, split ' ', $Config{locincpth};
  }
  if ($^O =~ /win32/i && $Config{cc} =~ /\bcl\b/i) {
    push(@incs, split /;/, $ENV{INCLUDE}) if exists $ENV{INCLUDE};
  }
  if ($Config{incpath}) {
    push @incs, grep -d, split /\Q$Config{path_sep}/, $Config{incpath};
  }
  push @incs, grep -d,
      qw(/sw/include 
         /usr/include/freetype2
         /usr/local/include/freetype2
         /usr/local/include/freetype1/freetype
         /usr/include /usr/local/include /usr/include/freetype
         /usr/local/include/freetype);
  if ($Config{ccflags}) {
    my @hidden = map { /^-I(.*)$/ ? ($1) : () } split ' ', $Config{ccflags};
    push @incs, @hidden;
    @definc{@hidden} = (1) x @hidden;
  }

  @libs= ( split(/\Q$Config{path_sep}/,$LIBPATH),
    map _tilde_expand($_), map { split /\Q$Config{path_sep}/} @libpaths );
  if ($Config{loclibpth}) {
    push @libs, grep -d, split ' ', $Config{loclibpth};
  }
  
  push @libs, grep -d, qw(/sw/lib),  split(/ /, $Config{'libpth'});
  push @libs, grep -d, split / /, $Config{libspath} if $Config{libspath};
  if ($^O =~ /win32/i && $Config{cc} =~ /\bcl\b/i) {
    push(@libs, split /;/, $ENV{LIB}) if exists $ENV{LIB};
  }
  if ($^O eq 'cygwin') {
    push(@libs, '/usr/lib/w32api') if -d '/usr/lib/w32api';
    push(@incs, '/usr/include/w32api') if -d '/usr/include/w32api';
  }
  if ($Config{ldflags}) {
    # some builds of perl put -Ldir into ldflags without putting it in
    # loclibpth, let's extract them
    my @hidden = grep -d, map { /^-L(.*)$/ ? ($1) : () }
      split ' ', $Config{ldflags};
    push @libs, @hidden;
    # don't mark them as seen - EU::MM will remove any libraries
    # it can't find and it doesn't look for -L in ldflags
    #@deflib{@hidden} = @hidden;
  }
  push @libs, grep -d, qw(/usr/local/lib);

  $formats{FT1}=
    {
     order=>'31',
     def=>'HAVE_LIBTT',
     objfiles=>'fontft1.o',
     LIBS => "-lttf",
     docs=>q{
Freetype 1.x supports Truetype fonts and is obsoleted by Freetype 2.x.

It's probably insecure.
}
		       };
  # Make fix indent
  for (keys %formats) { $formats{$_}->{docs} =~ s/^\s+/  /mg; }
}



sub gen {
  my $V = $ENV{$_[0]};
  print "  $_[0]: '$V'\n"
      if $VERBOSE && defined $V;
  defined($V) ? $V : "";
}


# Get information from environment variables

sub getenv {

  $VERBOSE ||= gen("IM_VERBOSE");

  print "Environment config:\n" if $VERBOSE;

  ($INCPATH,
   $LIBPATH,
   $NOLOG,
   $DEBUG_MALLOC,
   $MANUAL,
   $CFLAGS,
   $LFLAGS,
   $DFLAGS) = map { gen $_ } qw(IM_INCPATH
				IM_LIBPATH
				IM_NOLOG
				IM_DEBUG_MALLOC
				IM_MANUAL
				IM_CFLAGS
				IM_LFLAGS
				IM_DFLAGS);
}

# populate the environment so that sub-modules get the same info
sub setenv {
  $ENV{IM_VERBOSE} = 1 if $VERBOSE;
  $ENV{IM_INCPATH} = join $Config{path_sep}, @incpaths if @incpaths;
  $ENV{IM_LIBPATH} = join $Config{path_sep}, @libpaths if @libpaths;
}

sub make_imconfig {
  my ($defines) = @_;

  open my $config, "> imconfig.h"
    or die "Cannot create imconfig.h: $!\n";
  print $config <<EOS;
/* This file is automatically generated by Makefile.PL.
   Don't edit this file, since any changes will be lost */

#ifndef IMAGER_IMCONFIG_H
#define IMAGER_IMCONFIG_H

#ifdef __cplusplus
extern "C" {
#endif

EOS
  for my $define (@$defines) {
    if ($define->[2]) {
      print $config "\n/*\n  $define->[2]\n*/\n\n";
    }
    print $config "#define $define->[0] $define->[1]\n";
  }
  if ($Config{gccversion} && $Config{gccversion} =~ /^([0-9]+)/ && $1 > 3) {
    print $config <<EOS;
/*

Compiler supports the GCC __attribute__((format...)) syntax.

*/

#define IMAGER_FORMAT_ATTR 1

EOS
  }

  if ($Config{d_snprintf}) {
    print $config <<EOS;
/* We can use snprintf() */
#define IMAGER_SNPRINTF 1

EOS
  }

  if ($Config{d_vsnprintf}) {
    print $config <<EOS;
/* We can use vsnprintf() */
#define IMAGER_VSNPRINTF 1

EOS
  }

  print $config <<EOS;
/*
 Type and format code for formatted output as with printf.

 This is intended for formatting i_img_dim values.
*/
typedef $Config{ivtype} i_dim_format_t;
#define i_DF $Config{ivdformat}
EOS

  if ($Config{d_static_inline}) {
    print $config "#define IMAGER_STATIC_INLINE $Config{perl_static_inline}\n";
  }
  else {
    print $config "#define IMAGER_STATIC_INLINE static\n";
  }
  print $config <<'EOS';

#ifdef __cplusplus
}
#endif

#endif
EOS
  close $config;
}

sub usage {
  print STDERR <<EOS;
Usage: $0 [--enable feature1,feature2,...] [other options]
       $0 [--disable feature1,feature2,...]  [other options]
       $0 --help
Possible feature names are:
  T1-fonts
Other options:
  --verbose | -v
    Verbose library probing (or set IM_VERBOSE in the environment)
  --nolog
    Disable logging (or set IM_NOLOG in the environment)
  --incpath dir
    Add to the include search path
  --libpath dir
    Add to the library search path
  --coverage
    Build for coverage testing.
  --assert
    Build with assertions active.
EOS
  exit 1;

}

# generate the PM MM argument
# I'd prefer to modify the public version, but there doesn't seem to be 
# a public API to do that
sub gen_PM {
  my %pm;
  my $instbase = '$(INST_LIBDIR)';

  # first the basics, .pm and .pod files
  $pm{"Imager.pm"} = "$instbase/Imager.pm";

  my $mani = maniread();

  for my $filename (keys %$mani) {
    if ($filename =~ m!^lib/! && $filename =~ /\.(pm|pod)$/) {
      (my $work = $filename) =~ s/^lib//;
      $pm{$filename} = $instbase . $work;
    }
  }

  # need the typemap
  $pm{typemap} = $instbase . '/Imager/typemap';

  # and the core headers
  for my $filename (keys %$mani) {
    if ($filename =~ /^\w+\.h$/) {
      $pm{$filename} = $instbase . '/Imager/include/' . $filename;
    }
  }

  # and the generated header
  $pm{"imconfig.h"} = $instbase . '/Imager/include/imconfig.h';

  \%pm;
}

my $home;
sub _tilde_expand {
  my ($path) = @_;

  if ($path =~ m!^~[/\\]!) {
    defined $home or $home = $ENV{HOME};
    if (!defined $home && $^O eq 'MSWin32'
       && defined $ENV{HOMEDRIVE} && defined $ENV{HOMEPATH}) {
      $home = $ENV{HOMEDRIVE} . $ENV{HOMEPATH};
    }
    unless (defined $home) {
      $home = eval { (getpwuid($<))[7] };
    }
    defined $home or die "You supplied $path, but I can't find your home directory\n";
    $path =~ s/^~//;
    $path = File::Spec->catdir($home, $path);
  }

  $path;
}

sub _ft1_test_code {
  return <<'CODE';
TT_Engine engine;
TT_Error error;

error = TT_Init_FreeType(&engine);
if (error) {
   printf("FT1: Could not initialize engine\n");
   exit(1);
}

return 0;
CODE
}

sub map_bundled {
  my (@names) = @_;

  @names = map { split /,/ } @names;

  my @outnames;
  for my $name (@names) {
    push @outnames, $name;
    push @outnames, $bundled_names{$name}
      if $bundled_names{$name};
  }

  @outnames;
}

1;