use 5.010;
use alienfile;
use Sort::Versions;
use Env qw /@PATH @LD_LIBRARY_PATH @DYLD_LIBRARY_PATH @PROJ_LIB/;
use Path::Tiny qw /path/;

#  note to self for cmake builds:
#  https://github.com/OSGeo/gdal/pull/5485


my $on_windows = $^O =~ /mswin/i;
my $on_automated_rig
  =  $ENV{PERL_CPAN_REPORTER_DIR}
  || $ENV{PERL_CPAN_REPORTER_CONFIG}
  || $ENV{AUTOMATED_TESTING}
  || $ENV{TRAVIS}
  || $ENV{APPVEYOR}
  || $ENV{CI};

my @alien_deps = qw(
        Alien::geos::af
        Alien::proj
        Alien::sqlite
        Alien::libtiff
  );

#  make sure we get GEOS, PROJ and other support
foreach my $alien (@alien_deps) {
  eval "require $alien";
  my $e = $@;
  warn $e if $e;
  if (!$e && $alien->install_type eq 'share') {
    log "Prepending $alien bin dir to path";
    unshift @PATH, $alien->bin_dir;
  }
}
my $have_spatialite = eval 'require
  Alien::spatialite';
if ($have_spatialite && Alien::spatialite->version ge 5) {
  if (!$on_windows and Alien::spatialite->install_type('share')
      and $Alien::spatialite::VERSION lt 1.06) {
    $have_spatialite = undef;  #  rpath issues before this
  }
  else {
    log "Prepending Alien::spatialite bin dir to path";
    unshift @PATH, Alien::spatialite->bin_dir;
    push @alien_deps, 'Alien::spatialite';
  }
}
else {
  $have_spatialite = undef;
}

#  configure script does not seem to detect the proj lib distributed with Strawberry perl
#use FFI::CheckLib;
#my $proj_lib = FFI::CheckLib::find_lib (lib => 'proj');
Alien::Build->log ('$ENV{PROJ_LIB} IS ' . ($ENV{PROJ_LIB} // ''));


#  make libtool noisy for debug purposes
#$ENV{LTFLAGS} = "--debug --verbose" if $on_windows;


use Cwd;
my $base_dir = getcwd();
my $patch_file_isnan
  = "$base_dir/patch_isnan_gcc.patch";
my $patch_file_configure
  = "$base_dir/0001-Enable-shared-build-on-freebsd-10.patch";

my $min_target_version = '3.1.0';

plugin 'PkgConfig' => (
    pkg_name => 'gdal',
    minimum_version => $min_target_version,
);


share {

  #  add existing alien files to path etc
  #  not very elegant...
  #  disable for now - config tests fail
  #  it looks to be already noted in https://github.com/Perl5-Alien/Alien-Build/issues/12
  #  might also be able to access via old hash via https://metacpan.org/pod/Alien::Build#install_prop
  my $have_alien_gdal = eval 'require Alien::gdal';
  my $copy_from_dir;
  if ($ENV{ALIEN_SHARE_RECYCLE} && $have_alien_gdal && Alien::gdal->install_type eq 'share') {
      my $ag_version = Alien::gdal->version;
      say "Found existing gdal via alien ($ag_version) in " .  Alien::gdal->dist_dir;
      #  append the relevant path
      if (versioncmp($ag_version, $min_target_version) >= 0) {
        $copy_from_dir = Alien::gdal->dist_dir;
      }
  }

  if ($copy_from_dir) {
    #  files are copied in the extract hook
    meta->register_hook(download => sub {
      Path::Tiny->new ('fauxdownload.txt')->touch;
    });

    if($^O eq 'MSWin32') {
      meta->register_hook(extract => sub {
        my($build) = @_;
        $copy_from_dir =~ s|/|\\|g;  #  it seems that xcopy needs backslashes in paths
        $build->system("xcopy $copy_from_dir . /E /Q");
      });
    }
    else {
      meta->register_hook(extract => sub {
        my ($build) = @_;
        "cp -aR $copy_from_dir .";
      });
    }

    meta->after_hook( extract => sub {
      my($build) = @_;
      $build->log('CURRENTLY IN ' . cwd());
      File::Path::rmtree '_alien';
    });

    plugin 'Build::Copy';

  }
  else {
    my $with_local = '';
    my $with_cpp11 = '';
    
    Alien::Build->log ("Proj library version is " . Alien::proj->version);
    die "Proj version must be at least 6.0.0"
      if not Alien::proj->atleast_version ('6.0.0');  

    plugin 'Build::SearchDep' => (
      aliens   => [@alien_deps],
      public_I => 1,
      public_l => 1,
    );
  
    start_url 'http://download.osgeo.org/gdal/CURRENT';
    #start_url "file://$base_dir";  #  debug
    plugin Download => (
      filter  => qr/^gdal-([0-9\.]+)\.tar\.gz$/,
      version => qr/^gdal-([0-9\.]+)\.tar\.gz$/,
    );
  
    my $gdal_version = get_gdal_version() // 'not yet defined';
    say "Downloaded gdal version is $gdal_version";
    
    plugin Extract => (format => 'tar.gz');
  
    plugin 'Build::Autoconf' => ();
  
    my $build_static = ($^O =~ /mswin/i) ? '' : '--disable-shared';
    $build_static = '';
    $build_static = '--enable-static=no';  #  override - HUGE files if static
    $build_static = '' if $ENV{FORCE_DYNAMIC};
    
    
    if ($^O =~ /bsd|dragonfly/) {
      plugin 'Build::Make' => 'gmake';
      if (-d '/usr/local') {
          $with_local = '--with-local=/usr/local';
      }
    }
  
  
    my $make_cmd = '%{make}';
    my $make_inst_cmd = '%{make} install';  
    my @automated_rig_config_args;
    
    #  try not to exceed the cpan-testers log limits
    if ($on_automated_rig) {
      say "Running under CI or automated testing";
      $make_cmd      .= q/ | perl -ne "BEGIN {$|=1; open our $log, q|>|, q|build.log|};   print qq|\n| if 0 == ($. %% 100); print q|.|; print {$log} $_;" /;
      $make_inst_cmd .= q/ | perl -ne "BEGIN {$|=1; open our $log, q|>|, q|install.log|}; print qq|\n| if 0 == ($. %% 100); print q|.|; print {$log} $_;" /;
      if (!$on_windows) {
          $make_cmd =~ s/%%/%/;
          $make_cmd =~ s/"/'/g;
          $make_cmd .= ' || (E=$? && cat build.log && exit $E)';
          $make_inst_cmd =~ s/%%/%/;
          $make_inst_cmd =~ s/"/'/g;
          $make_inst_cmd .= ' || (E=$? && cat install.log && exit $E)';
      }
      else {
          #  fake exit codes on failure, not sure how to get originals
          $make_cmd      .= q/ || perl -e"system ('type', 'build.log'); exit 1"/;
          $make_inst_cmd .= q/ || perl -e"system ('type', 'install.log'); exit 1"/;
      }
      if (! ($ENV{TRAVIS} || $ENV{APPVEYOR} || $ENV{CI})) {
          #  make build slightly faster and smaller on cpan testers
          push @automated_rig_config_args, '--with-gnm=no';
      }
      #  debug
      push @automated_rig_config_args,
        (qw/--with-openjpeg=no --with-jasper=no/);
  
      #  clean up the build dir on cpan testers etc
      plugin 'Cleanse::BuildDir';
    }
    
    my @with_args = (
      #'--with-libtiff=internal',
      $with_cpp11,
      $with_local,
      @automated_rig_config_args,
      #'--without-pg',
      '--without-ogdi',
      '--with-mongocxxv3=no',
      $^O eq 'MSWin32' ? '--without-hdf5' : (),  #  issues with strawberry perl hdf5 dlls
    );
    my @ld_flags;
    my @ld_lib_path;
    #  system installs won't necessarily have dist dirs
    if (Alien::proj->install_type eq 'share') {
      push @with_args, '--with-proj=' . Alien::proj->dist_dir;
      my $proj_lib = Alien::proj->dist_dir . '/share/proj';
      warn "======\nno proj lib\n=====" if !-e $proj_lib;
      if (!grep {($_ // '')=~ /^$proj_lib$/} @PROJ_LIB) {
        Alien::Build->log ("Appending $proj_lib to \$ENV{PROJ_LIB}");
        push @PROJ_LIB, $proj_lib;
        Alien::Build->log ("PROJ_LIB is now $ENV{PROJ_LIB}");
      }
      if (!$on_windows) {
        push @ld_lib_path, Alien::proj->dist_dir . '/lib';
      }
      #  get dynamic proj extra deps, one day will not be needed
      push @with_args,
        q{--with-proj-extra-lib-for-test="} . Alien::proj->libs . q{"};
    }
    if (Alien::libtiff->install_type eq 'share') {
      push @with_args, '--with-libtiff=' . Alien::libtiff->dist_dir;
      if (!$on_windows) {
        push @ld_lib_path, Alien::libtiff->dist_dir . '/lib';
      }
    }
    if ($have_spatialite && Alien::spatialite->install_type eq 'share') {
      push @with_args, '--with-spatialite=' . Alien::spatialite->dist_dir;
    }
    if (Alien::sqlite->install_type eq 'share') {
      push @with_args, '--with-sqlite3=' . Alien::sqlite->dist_dir;
      if (!$on_windows) {
        push @ld_lib_path, Alien::sqlite->dist_dir . '/lib';
      }
      #push @ld_flags, 'LDFLAGS=' . Alien::sqlite->libs;
      #'-L/home/libraries/intel/sqlite/lib -lsqlite3'
    }
    if (Alien::geos::af->install_type eq 'share') {
      push @with_args,
          '--with-geos='
        . Alien::geos::af->bin_dir
        . '/geos-config';
      #  build weirdness on linux with geos under cmake
      if (!$on_windows) {
        push @ld_lib_path, Alien::geos::af->dist_dir . '/lib';
      }
    }
    #  an attempt to get hdf5 support under strawberry,
    #  but which did not work so now it is turned off
    #if ($^O eq 'MSWin32' && !$ENV{HDF5_LIBS}) {
    #  my $c_dir = path ($^X)->parent->parent->parent->child('c');
    #  if ($c_dir->exists) {
    #    #$ENV{HDF5_LIBS} = "-L$c_dir/lib -lhdf5";
    #    #warn 'adding HDF5_LIBS var: ' . $ENV{HDF5_LIBS};
    #    push @with_args, '--without-hdf5';
    #    #push @ld_flags, "-L$c_dir/lib -lhdf5";
    #  }
    #}
    if ($^O eq 'MSWin32') {
      unshift @with_args, '--with-hide-internal-symbols=yes';
      push @with_args, '--without-gnm';
      #  should add an env flag for this
      push @with_args, '--with-curl=no';
    }    
    
    
    my $config_args = $ENV{ALIEN_GDAL_CONFIG_ARGS} // '';
    $config_args =~ s/[^-\s\w,=+]//g;  #  overkill?
    $config_args = join (' ', @with_args) . " $config_args";
  
    if (versioncmp ($gdal_version, '3.1.1') < 0) {
      meta->before_hook( build => \&patch_makefile_long_line );
    }
    
    meta->around_hook( build => \&remove_gitfw_from_path );
    plugin 'PkgConfig::PPWrapper';
    meta->around_hook(
      build => sub {
        my ($orig, $build, @args) = @_;
        $build->log("Setting CCACHE_BASEDIR to " . getcwd());
        local $ENV{CCACHE_BASEDIR} = getcwd();
        $orig->($build, @args);
      }
    );

    #meta->before_hook( build => \&cleanup_ldflags );
    
    my $drivers_to_disable = join ' ', get_drivers_to_disable();
    ## --with-curl=no
    #  not sure this is needed, or if it is to deal with a cirrus quirk 
    #local $ENV{LDFLAGS} = join ' ', @ld_flags;
    #  hack
    if ($^O =~ /darwin/) {
      push @DYLD_LIBRARY_PATH, @ld_lib_path;
    }
    else {
      push @LD_LIBRARY_PATH, @ld_lib_path;
    }

    #foreach my $env_var (qw /LD_LIBRARY_PATH DYLD_LIBRARY_PATH LDFLAGS CFLAGS CXXFLAGS/) {
    #  say "ENV: $env_var is " . ($ENV{$env_var} // '');
    #}
    #$ENV{CFLAGS} = ' -O2 ';
    my $ld_flags = '';
    my $config_flags = qq{CFLAGS='-O2' CXXFLAGS='-O2' LDFLAGS='$ld_flags'};
    
    build [
      #\&update_pkg_conf_path,
      \&cleanup_ldflags,
      "%{configure} $config_flags $config_args $drivers_to_disable $build_static",
      \&pause,
      $make_cmd,
      \&patch_rpaths,
      \&patch_pkgconfig,
      $make_inst_cmd,
    ];
  }
};

sub patch_rpaths {
  my ($build) = @_;
  
  #  only run on unices - incomplete check but
  #  I don't think aliens work on VMS or zOS
  return if ($on_windows or $^O =~ /darwin/i);

  my $h = get_alien_state_hash();
  my $install_path = $h->{install}{prefix};
  return if !defined $install_path;

  my @alien_rpaths;
  for my $alien (@alien_deps) {
    next if not $alien->install_type('share');
    push @alien_rpaths, $alien->dist_dir . '/lib';
  }
  if (!@alien_rpaths) {
    $build->log('No shared alien deps found, not updating rpaths');
    return;
  }

  eval 'require Alien::patchelf'
    or do {
      warn 'Unable to load Alien::patchelf, cannot update rpaths';
      return;
    };
  my $pe = Alien::patchelf->new;


  my $origin_string = $^O =~ /darwin/ ? '@loader_path' : '$ORIGIN';
  my $alien_rpath_text
    = join ':', (
       (map {$origin_string . '/../' . path ($_)->relative($install_path)->stringify} @alien_rpaths),
       @alien_rpaths
      );

  $build->log ("Prepending rpaths with $alien_rpath_text");
  
  #  need to also do the bin dir - those files only need to have $ORIGIN/../lib added
  use File::Find::Rule;
  my (@so_files)
    = grep {not -l $_}
      File::Find::Rule
              ->file()
              ->name( qr/^libgdal.so/ )
              ->in( getcwd() );
  
  foreach my $so_file (@so_files) {
    my ($old_rpath, $result, $stderr, @errors);
    ($old_rpath, $stderr, @errors)
      = $pe->patchelf ('--print-rpath', $so_file);
    $old_rpath //= '';
    #  prepend our paths
    my $rpath = $alien_rpath_text . ($old_rpath ? (':' . $old_rpath) : '');
    $build->log("Updating rpath for $so_file to $rpath, was $old_rpath");
    ($result, $stderr, @errors)
      = $pe->patchelf ('--set-rpath', $rpath, $so_file);
    warn $stderr if $stderr;
  }
  
  #  surely we could just use one of the alien props to get the dir?
  my (@gdalinfo_files)
    = grep {not -l $_}
      grep {$_ =~ m|apps/\.libs|}  #  must be in the .libs dir
      File::Find::Rule
              ->file()
              ->name( 'gdalinfo' )
              ->in( getcwd() );
  my @binaries
    = grep {$_ !~ /\.o$/}  #  no object files
      map {glob (path($_)->parent->stringify . '/*')} @gdalinfo_files;

  foreach my $so_file (@binaries) {
    my ($old_rpath, $result, $stderr, @errors);
    ($old_rpath, $stderr, @errors)
      = $pe->patchelf ('--print-rpath', $so_file);
    $old_rpath //= '';
    #  prepend relative path
    my $rpath = '$ORIGIN/../lib' . ($old_rpath ? (':' . $old_rpath) : '');
    $build->log("Updating rpath for $so_file to $rpath, was $old_rpath");
    ($result, $stderr, @errors)
      = $pe->patchelf ('--set-rpath', $rpath, $so_file);
    warn $stderr if $stderr;
  }

  return;
}


#  we can get dangling -L values
#  bandaid until we find the source
sub cleanup_ldflags {
    my ($build, @args) = @_;

    if ($ENV{LDFLAGS} =~ /\s*-L\s*$/) {
        $build->log("Trimming trailing -L from $ENV{LDFLAGS}");
        $ENV{LDFLAGS} =~ s/\s*-L\s*$//;
    }

    #$orig->($build, @args);
    return;
}


sub update_makefile {
    return;  #  should not be needed now
    return if !$on_windows;
    system ($^X, '-p', '-i.bak', q{-e"s/\$\(GDAL_ROOT\)/\./"}, "GNUmakefile");
}

sub update_pkg_conf_path {
    return if !$on_windows;
    use Env qw /@PKG_CONFIG_PATH/;
    say 'Modifying drive paths in PKG_CONFIG_PATH';
    say $ENV{PKG_CONFIG_PATH};
    #  msys-ificate drive paths
    @PKG_CONFIG_PATH = map {my $x = $_; $x =~ s{^([a-z]):}{/$1}i; $x} @PKG_CONFIG_PATH;
    say $ENV{PKG_CONFIG_PATH};
    return;
}


sub patch_makefile_long_line {
    my ($build) = @_;

    #return if !$on_windows;
    
    my $target = "GNUmakefile";
    $build->log ("Patching $target to cope with long lines");
    #my $target_line = quotemeta q{$(LD) $(LDFLAGS) $(LIBS) -o $@ $(sort $(wildcard $(GDAL_OBJ:.o=.lo))) \ };
    
    my $text_target1 = '$(LIBGDAL):	$(GDAL_OBJ:.o=.lo)';
    my $x = quotemeta $text_target1;
    my $qr_target1 = qr /^$x/;
    my $new_contents1 = <<'NEW_CONTENTS'
SORTED  := $(sort $(wildcard $(GDAL_OBJ:.o=.lo)))
NSORTED := $(words $(SORTED))
#  mid left and right indices
MIDL := $(shell echo $$(( $(NSORTED) / 2 )) )
MIDR := $(shell echo $$(( $(MIDL) + 1 )) )
NEW_CONTENTS
  ;
    my $qr_target2 = qr /sort .+wildcard/;
    my $new_contents2 = << 'NEW_CONTENTS'
$(LD) $(LDFLAGS) $(LIBS) -o $@ \
$(wordlist 1,$(MIDL),$(SORTED)) \
$(wordlist $(MIDR),$(NSORTED),$(SORTED)) \
NEW_CONTENTS
  ;
    $new_contents2 =~ s/^/\t/gms;
    open my $fh , '<', $target
      or die "Could not open GNUmakefile for reading, $!";
    my $file_contents;
    
    while (my $line = <$fh>) {
        if ($line =~ $qr_target1) {
            $file_contents .= $new_contents1;
        }
        elsif ($line =~ $qr_target2) {
            $line = $new_contents2;
        };
        $file_contents .= $line;
    }
    $fh->close;
    rename $target, "$target.bak";
    open my $ofh, '>', $target
      or die "Could not open $target for writing, $!";
    print {$ofh} $file_contents;
    return;
}

#  git for windows clashes with MSYS
#  if its /usr/bin dir is in the path
sub remove_gitfw_from_path {
  my ($orig, $build, @args) = @_;

  return $orig->($build, @args)
    if !$on_windows;

  local $ENV{PATH} = $ENV{PATH};

  my $msys_path = eval {
    path('Alien::MSYS'->msys_path())
  };
  return if !defined $msys_path;
  my $count = @PATH;

  @PATH
    = grep {path($_)->stringify =~ m|/usr/bin$| && path($_) ne $msys_path ? () : $_}
      @PATH;

  my $removed = $count - @PATH;
  if ($removed) {
    $build->log ("$removed additional .../usr/bin dirs were removed from the path for compilation");
  }

  $orig->($build, @args);
}

sub pause {
    return;  #  re-enable in case of debug
    return if $on_automated_rig;
    return if !$on_windows;

    say "CONTINUE?";
    my $response = <>;
    while (not $response =~ /yes/) {
        $response = <>;
    }
}


sub patch_pkgconfig {
    #my $gdal_config_file = 'bin/gdal-config';
    #my $pkg_config_file  = 'lib/pkgconfig/gdal.pc';
    use File::Find::Rule;
    my @gdal_configs
      = File::Find::Rule->file()
                        ->name( 'gdal-config' )
                        ->in( '.' );
    my @pkg_configs
      = File::Find::Rule->file()
                        ->name( 'gdal.pc' )
                        ->in( '.' );
    say 'gdal-configs: ' . join ' ', @gdal_configs;
    say 'pkg-configs:  ' . join ' ', @pkg_configs;
    
    return if !@gdal_configs || !@pkg_configs;
    
    open my $gc_fh, '<', $gdal_configs[0] or die $!;
    my $dep_libs = '';
    while (defined (my $line = <$gc_fh>)) {
        if ($line =~ /CONFIG_DEP_LIBS=(.+)$/) {
            $dep_libs = $1;
            last;
        }
    }
    close $gc_fh;

    #  trim quotes (could do in prev check, but...)
    $dep_libs =~ s/^\"//;
    $dep_libs =~ s/\"$//;
    
    open my $pk_fh, '<', $pkg_configs[0] or die $!;
    my @pkg_conf;
    while (defined (my $line = <$pk_fh>)) {
        push @pkg_conf, $line;
    }
    close $pk_fh;

    #  change all (we should be more nuanced and do only the one that matters)
    foreach my $pkg_config_file (@pkg_configs) {
        say "Adding gdal dep_libs to $pkg_config_file";
        #  now add the dep libs to the pkg_conf file
        open $pk_fh, '>', $pkg_config_file or die $!;
        foreach my $line (@pkg_conf) {
            if ($line =~ /^CONFIG_INST_LIBS/) {
                chomp $line;
                $line .= " $dep_libs\n";
            }
            print {$pk_fh} $line;
        }
        close $pk_fh;
    }
}

sub get_gdal_version {
    my $h = get_alien_state_hash();
    return $h->{runtime}{version};
}

sub get_alien_state_hash {
    use JSON::PP;
    my $root = "$base_dir/_alien";
    my $f = "$root/state.json";
    my $h = {};
    if (-e $f) {
        open my $fh, '<', $f or die $!;
        my $d = do {
            local $/ = undef;
            <$fh>;
        };
        $h = JSON::PP::decode_json($d);
    }
    return $h;
}


sub get_drivers_to_disable {
  #  some of these are already disabled in recent versions of GDAL
  my @drivers = qw /
    e00grid  jdem     xpm       grib
    zmap     adrg     arg       blx
    bt       cals     ceos      coasp
    cosar    ctg      dimap     elas
    fit      gff      gsg       gxf
    hf2      iris     lan       leveller
    mrf      msgn     northwood prf
    rmf      rs2      sgi       sigdem
    stacta   terragen til       rik
    ozi      webp
    
    aeronavfaa arcgen   bna    couchdb
    geomedia   gtm      htf    openair
    cloudant   rec      sdts   sua
    walk       tiger    edigeo
    geoconcept georss   gmt    jml
    s57        wasp     vdv
  /;
  my @args = map {"--disable-driver-$_"} @drivers;
  return wantarray ? @args : \@args;
}
