use 5.010;
use alienfile;
use Sort::Versions;
use Path::Tiny qw /path/;
use File::Which qw /which/;
use FFI::CheckLib;

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};


use Cwd;
my $base_dir = getcwd();

use Env qw( @PATH @PKG_CONFIG_PATH );

use Alien::sqlite;
unshift @PATH, Alien::sqlite->bin_dir;
my @dep_aliens = ('Alien::sqlite');
my %have;
my $lib = 'Alien::curl';
$have{$lib} = eval "require $lib";
if ($have{$lib}) {
    unshift @PATH, $lib->dist_dir . '/dynamic';
}
$lib = 'Alien::libtiff';
$have{$lib} = eval "require $lib";
if ($have{$lib}) {
    #say 'Adding libtiff dependency';
    unshift @PATH, $lib->bin_dir;
    push @dep_aliens, $lib if $lib->install_type eq 'share';
    my $p;
    if ($on_windows && $lib->install_type eq 'system') {
      #  dirty hack for strawberry perl
      $p = path ($^X)->parent->parent->parent . '/c/lib/pkgconfig';
    }
    elsif ($lib->install_type eq 'share') {
      $p = path ($lib->dist_dir, 'lib', 'pkgconfig');
    }
    if ($p && -e $p) {
      push @PKG_CONFIG_PATH, $p;
      say join ' ', @PKG_CONFIG_PATH;
    }
}

say "Alien::sqlite has sqlite version " . Alien::sqlite->version;

my $min_target_version = '7.1';

plugin 'PkgConfig' => (
    pkg_name => 'proj',
    minimum_version => $min_target_version,
);
if ($on_windows) {
  #  possible GIS Internals distribution
  if (my $path = `where geos_c.dll`) {
    Alien::Build->log ('Found possible GIS Internals build');
    $path = Path::Tiny::path ($path)->parent;
    #  could be several variants
    my @proj_dlls
      = map {$_->[0]}
        sort { versioncmp($a->[-1], $b->[-1]) }
        map  { $_ =~ /(\d+)_(\d+)/; [$_, "$1.$2"] }
        glob ("$path/proj*.dll");
    $proj_dlls[-1] =~ /(\d+)_(\d+)/;
    my $version = "$1.$2";
    if (versioncmp ($version, $min_target_version) > 0) {
      $path = Path::Tiny::path($proj_dlls[-1])->parent->stringify;
      $path =~ s|\\|/|g;  #  path separators
      Alien::Build->log ("Probe::CBuilder checking $path");
      plugin 'Probe::CBuilder' => (
        cflags => "-I$path/include",
        libs   => "-L$path/lib -lproj",
      );
    }
  }
}


share {

  #  see if this helps with cirrus bsd builds
  #$ENV{SQLITE3_CFLAGS} = Alien::sqlite->cflags;
  #$ENV{SQLITE3_LIBS} = Alien::sqlite->libs;
  #say "sqlite cflags: " . Alien::sqlite->cflags;
  #say "sqlite libs: " . Alien::sqlite->libs;
  #if ($have{'Alien::libtiff'}) {
  #  my $p = path ('Alien::libtiff'->dist_dir, 'lib');
  #  $ENV{TIFF_LIBS}
  #    ||= (-e $p
  #          ? "-L$p "
  #          : ''
  #        )
  #        . 'Alien::libtiff'->libs;
  #  say "libtiff libs: $ENV{TIFF_LIBS}";
  #}

  plugin 'Build::SearchDep' => (
    aliens   => [ @dep_aliens ],
    public_I => 1,
    public_l => 1,
  );


  if ($on_windows) {
    #  there are issues with strawberry perl's gcc
    plugin 'Prefer::BadVersion' => '7.1.0';
  }
  
  start_url 'https://download.osgeo.org/proj/';
  #start_url "file://$base_dir";  #  debug
  plugin Download => (
    filter  => qr/^proj-([0-9\.]+)\.tar\.gz$/,
    version => qr/^proj-([0-9\.]+)\.tar\.gz$/,
  );

  my $proj_version = get_proj_version() // 'not yet defined';
  say "Downloaded proj version is $proj_version";
  
  die "Downloaded proj version $proj_version is too low "
      . "(should be >= $min_target_version).\n"
      . "Please update your Alien::Build::Fetch::Cache if using one."
    if   defined $proj_version
      && versioncmp ($proj_version, $min_target_version) < 0;
  
  plugin Extract => (format => 'tar.gz');


  plugin 'Build::CMake' => ();
  plugin 'Build::MSYS';
  
  
  if ($^O =~ /bsd|dragonfly/) {
    plugin 'Build::Make' => 'gmake';
    if (!-e '/usr/local/include/sqlite3.h' && Alien::sqlite->install_type eq 'system') {
      warn '/usr/local/include/sqlite3.h does not exist, '
         . 'you might need to install the sqlite package for your system, '
         . 'or install a share version of Alien::sqlite';
    }
  }
  elsif ($^O eq 'MSWin32') {
    plugin 'Build::Make' => 'gmake';
  }

  my $make_cmd = '%{make}';
  my $make_inst_cmd = '%{make} install';
  my @make_clean;
  #  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} $_;" || type build.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} $_;" || type install.log/;
    #if (!$on_windows) {
    #    $make_cmd =~ s/%%/%/;
    #    $make_cmd =~ s/type/cat/;
    #    $make_cmd =~ s/"/'/g;
    #    $make_inst_cmd =~ s/%%/%/;
    #    $make_inst_cmd =~ s/type/cat/;
    #    $make_inst_cmd =~ s/"/'/g;
    #}
    #if (! ($ENV{TRAVIS} || $ENV{APPVEYOR})) {
    #    push @make_clean, '%{make} clean';
    #}
    #  clean up the build dir on cpan testers etc
    #  but not github workflows
    if (!$ENV{CI}) {
      plugin 'Cleanse::BuildDir';
    }
  }
  
  my $config_args = $ENV{ALIEN_PROJ_CONFIG_ARGS} // '';
  $config_args =~ s/[^-\s\w,=]//g;  #  overkill?
  my $enable_tiff = 'OFF';
  my $enable_curl = 'OFF';
  my @curl_extra_args;
  my @tiff_extra_args;
  my @alien_rpaths;
  if ($proj_version ge 7) {
    if (!$have{'Alien::libtiff'} or $config_args =~ /--(without-tiff|with-tiff=no)/) {
      $enable_tiff = 'OFF';
      Alien::Build->log ('Disabling TIFF support');
    }
    else {
      $enable_tiff = 'ON';
      Alien::Build->log ('Enabling TIFF support');
      #  might need this but it depends on a few things
      my $libtiff_include;
      my $libtiff_rpath = ();
      if (Alien::libtiff->install_type ('share')) {
        $libtiff_include = path ('Alien::libtiff'->dist_dir . '/include')->stringify;
        $libtiff_rpath = Alien::libtiff->dist_dir . '/lib';
      }
      else {
        if ($on_windows) {
          my $p = path ($^X)->parent->parent->parent . '/c/include';
          if (path($p)->exists) {
            $libtiff_include = $p;
          }
        }
        else {
          my $libfile = find_lib (lib => 'tiff');
          push @tiff_extra_args, "-DTIFF_LIBRARY=$libfile";
        }
      }
      if ($libtiff_include) {
        my $libfile = find_lib_file (
          lib   => 'tiff',
          alien => 'Alien::libtiff',
        );
        push @tiff_extra_args, (
          "-DTIFF_LIBRARY=$libfile",
          "-DTIFF_INCLUDE_DIR=$libtiff_include",
        );
        push @alien_rpaths, $libtiff_rpath;
      }
      if (!@tiff_extra_args) {
        Alien::Build->log ('Disabling TIFF support');
        $enable_tiff = 'OFF';
      }
    }
    if (!$have{'Alien::curl'} or $config_args =~ /--(without-curl|with-curl=no)/ ) {
      $enable_curl = 'OFF';
    }
    else {
      my $dynamic_config = path ('Alien::curl'->dist_dir . '/dynamic/curl-config');
      if (-e $dynamic_config) {
        Alien::Build->log ("Adding curl support from " . 'Alien::curl'->dist_dir . "/dynamic");
        $enable_curl = "ON";
        push @PATH, path ('Alien::curl'->dist_dir . '/dynamic');
        my $curl_include = path ('Alien::curl'->dist_dir . '/include')->stringify;
        my $libfile = find_lib_file (
          lib   => 'curl',
          alien => 'Alien::curl',
        );
        push @curl_extra_args, (
          "-DCURL_LIBRARY=$libfile",
          "-DCURL_INCLUDE_DIR=$curl_include",
        );
        push @alien_rpaths, Alien::curl->dist_dir . '/dynamic';
      }
      elsif ('Alien::curl'->install_type eq 'system') {
        if ('Alien::curl'->cflags =~ /STATIC_LIB/) {
          $enable_curl = 'OFF';
          Alien::Build->log ('Disabling curl support.  You have Alien::curl but it lacks a dynamic curl-config');
        }
        else {
          Alien::Build->log ('Adding curl support from system');
          $enable_curl = "ON";
          my $libfile = find_lib_file (
            lib   => 'curl',
          );
          my $curl_include = Alien::curl->cflags;
          $curl_include =~s/^-I//g;  #  hack
          push @curl_extra_args, (
            "-DCURL_LIBRARY=$libfile",
            "-DCURL_INCLUDE_DIR=$curl_include",
          );
        }
      }
      else {
        Alien::Build->log ('Disabling curl support.  You have Alien::curl, but it lacks a dynamic curl-config');
        #  until we depend on an Alien::libcurl that provides a dynamic curl-config
        $enable_curl = 'OFF';  
      }
    }
  }
  my @sqlite_extra_args;
  if (Alien::sqlite->install_type ('share')) {
    Alien::Build->log ('Shared Alien::sqlite found, adding sqlite3 components to configure call');
    #  system install should have these in the relevant paths
    my $sqlite_include = path ('Alien::sqlite'->dist_dir . '/include')->stringify;
    my $libfile = find_lib_file (
        lib   => 'sqlite3',
        alien => 'Alien::sqlite',
    );
    my $exe_extension = ($on_windows ? '.exe' : '');
    my $sqlite_exe = path ('Alien::sqlite'->dist_dir . "/bin/sqlite3$exe_extension");
    push @sqlite_extra_args, (
      "-DEXE_SQLITE3=$sqlite_exe",
      "-DSQLite3_LIBRARY=$libfile",
      "-DSQLite3_INCLUDE_DIR=$sqlite_include",
    );
    push @alien_rpaths, Alien::sqlite->dist_dir . '/lib';
  }

  plugin 'PkgConfig::PPWrapper';
  meta->around_hook( build => \&remove_gitfw_from_path );
  meta->around_hook( build => \&update_cmake_lists_file );
  meta->around_hook(
    build => sub {
      my ($orig, $build, @args) = @_;
      $build->log("Setting CCACHE_BASEDIR to " . getcwd());
      local $ENV{CCACHE_BASEDIR} = getcwd();
      $orig->($build, @args);
    }
  );

  #  silence an undef warning
  Alien::Build->log ('PKG_CONFIG_PATH:' . join ' ', grep {defined} @PKG_CONFIG_PATH);

  foreach my $env_var (qw /LD_LIBRARY_PATH LDFLAGS CFLAGS CXXFLAGS/) {
    say "ENV: $env_var is " . ($ENV{$env_var} // '');
  }

  #  try to fix some rpath issues
  #  See also:
  #  https://github.com/OSGeo/gdal/issues/5413#issuecomment-1060286925
  my $h = get_alien_state_hash();
  my $install_path = $h->{install}{prefix};
  my @rpath;
  if (!$on_windows && defined $install_path && @alien_rpaths) {
    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;
    @rpath = (qq{-DCMAKE_INSTALL_RPATH='$alien_rpath_text'});
  }

  my $cmake_cmd = [
    '%{cmake}',
    -G => '%{cmake_generator}',
    '-DCMAKE_MAKE_PROGRAM=%{make}',
    #'-DBUILD_DOCUMENTATION=NO',
    '-DBUILD_TESTING=OFF',
    '-DBUILD_SHARED_LIBS=ON',
    @sqlite_extra_args,
    "-DENABLE_CURL=$enable_curl",
    $enable_curl eq 'ON' ? @curl_extra_args : (),
    "-DENABLE_TIFF=$enable_tiff",
    $enable_tiff eq 'ON' ? @tiff_extra_args : (),
    '-DBUILD_PROJSYNC=OFF',
    #'-DENABLE_IPO=ON',
    #'-DCMAKE_POSITION_INDEPENDENT_CODE:BOOL=true',
    '-DCMAKE_INSTALL_PREFIX:PATH=%{.install.prefix}',
    '-DCMAKE_BUILD_TYPE=Release',
    @rpath,
    #'-DCMAKE_VERBOSE=ON',
    '..'
  ];
    
  build [
    #\&pause,
    'mkdir _build',
    'cd _build',
    $cmake_cmd,
    $make_cmd,
    #($run_tests ? '%{make} test' : ()),
    $make_inst_cmd,
  ];


};


#  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 update_pkg_conf_path {
  return;
    return if !$on_windows;
    #  should be a before or around hook
    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 $r=$_; $r=~s{^([a-z]):}{/$1}i; $r} @PKG_CONFIG_PATH;
    #  make sure we get the dynamic libcurl
    #  (although the proj configure script does not currently use it)
    @PKG_CONFIG_PATH
      = map {my $r=$_; $r=~s{Alien-curl[/\\]lib[/\\]pkgconfig}{Alien-curl/dynamic/pkgconfig}i; $r}
        @PKG_CONFIG_PATH;
    $ENV{PKG_CONFIG_PATH} = join ':', @PKG_CONFIG_PATH;
    say $ENV{PKG_CONFIG_PATH};
    return;
}

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 update_cmake_lists_file {
    my ($orig, $build, @args) = @_;

    #  only needed on windows for v9.0.0 or earlier
    return $orig->($build, @args)
      if $on_windows and versioncmp (get_proj_version(), '9.0.0') < 0; 

    $build->log ('updating CMakeLists.txt to properly handle proj.pc');
    
    #my $h = $build->install_prop;
    #use Data::Printer;
    #p $h;
    
    use File::Find::Rule;
    my ($cmake_lists_file)
      = File::Find::Rule
                ->file()
                ->name( 'CMakeLists.txt' )
                ->in( $build->install_prop->{extract} );
    #$build->log ("====\ncmake file: $cmake_lists_file \n ====");
    open my $fh , '<', $cmake_lists_file
      or die "Could not open $cmake_lists_file for reading, $!";
    my $file_contents;

    my $define_check = <<EOD
if(NOT DEFINED CMAKE_INSTALL_LIBDIR)
  set(CMAKE_INSTALL_LIBDIR lib)
endif(NOT DEFINED CMAKE_INSTALL_LIBDIR)
EOD
  ;
  
    while (my $line = <$fh>) {
        if ($line =~ /^\s*configure_proj_pc/) {
#say STDERR "ADDING TO CMAKE LISTS"; 
          $line .= $define_check;
        }
        $file_contents .= $line;
    }
    $fh->close;

    rename $cmake_lists_file, "$cmake_lists_file.bak";
    open my $ofh, '>', $cmake_lists_file
      or die "Could not open $cmake_lists_file for writing, $!";
    print {$ofh} $file_contents;
    $ofh->close or die $!;

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


sub find_lib_file {
  my %args = @_;
  
  my(@lib_files) = find_lib(
      %args,
      systempath => $args{alien} ? [] : undef,  #  only search the alien if one is passed
  );
  #  we grab the first, assuming they are
  #  all the same or point to the same origin
  my $libfile = $lib_files[0];
  $libfile =~ s|\\|/|g if $on_windows;
  Alien::Build->log ("Found $args{lib} dynamic lib: $libfile");
  return $libfile;
}

sub get_proj_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;
}

