# Based on Module::Build::Tiny which is copyright (c) 2011 by Leon Timmermans, David Golden.
# Module::Build::Tiny is free software; you can redistribute it and/or modify it under
# the same terms as the Perl 5 programming language system itself.
use v5.38;
use feature 'class';
no warnings 'experimental::class', 'experimental::builtin';
$|++;
class    #
    Alien::SDL3::Builder {
    use CPAN::Meta;
    use ExtUtils::Install qw[pm_to_blib install];
    use ExtUtils::InstallPaths 0.002;
    use File::Basename        qw[basename dirname];
    use File::Find            ();
    use File::Path            qw[mkpath rmtree];
    use File::Spec::Functions qw[catfile catdir rel2abs abs2rel splitdir curdir];
    use JSON::PP 2            qw[encode_json decode_json];
    use Config;
    use Carp qw[croak];
    use Env  qw[@PATH];
    use HTTP::Tiny;

    # Not in CORE
    use Path::Tiny qw[cwd path tempdir];
    use ExtUtils::Helpers 0.028 qw[make_executable split_like_shell detildefy];
    use Devel::CheckBin;
    #
    field $action : param //= 'build';
    field $meta = CPAN::Meta->load_file('META.json');

    # https://wiki.libsdl.org/SDL3/Installation
    #~ apt-get install libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev
    #~ pacman -S sdl2 sdl2_image sdl2_mixer sdl2_ttf
    #~ dnf install SDL2-devel SDL2_image-devel SDL2_mixer-devel SDL2_ttf-devel
    #~ https://github.com/libsdl-org/setup-sdl/issues/20
    # TODO: Write a GH action to test with libs preinstalled
    field $version  : param //= '3.2.28';
    field $prebuilt : param //= 1;
    field $archive  : param //= sprintf 'https://github.com/libsdl-org/SDL/releases/download/release-%s/SDL3-' . (
        $^O eq 'MSWin32' ?
            !$prebuilt ?
                '%s.zip' :
                ( $Config{cc} =~ m[gcc]i ? 'devel-%s-mingw.zip' : 'devel-%s-VC.zip' ) :

            #~ $^O eq 'darwin' ? '%s.dmg' :
            '%s.tar.gz'
        ),
        $version, $version;
    field $http;
    field %config;
    #
    # Params to Build script
    field $install_base  : param //= '';
    field $installdirs   : param //= '';
    field $uninst        : param //= 0;    # Make more sense to have a ./Build uninstall command but...
    field $install_paths : param //= ExtUtils::InstallPaths->new( dist_name => $meta->name );
    field $verbose       : param //= 0;
    field $dry_run       : param //= 0;
    field $pureperl      : param //= 0;
    field $jobs          : param //= 1;
    field $destdir       : param //= '';
    field $prefix        : param //= '';
    field $cwd = cwd()->absolute;
    #
    #
    ADJUST {
        -e 'META.json' or die "No META information provided\n";
    }
    method write_file( $filename, $content ) { path($filename)->spew_raw($content) or die "Could not open $filename: $!\n" }
    method read_file ($filename)             { path($filename)->slurp_utf8         or die "Could not open $filename: $!\n" }

    method step_build() {
        $self->step_build_libs;
        for my $pl_file ( find( qr/\.PL$/, 'lib' ) ) {
            ( my $pm = $pl_file ) =~ s/\.PL$//;
            system $^X, $pl_file->stringify, $pm and die "$pl_file returned $?\n";
        }
        my %modules       = map { $_ => catfile( 'blib', $_ ) } find( qr/\.pm$/,  'lib' );
        my %docs          = map { $_ => catfile( 'blib', $_ ) } find( qr/\.pod$/, 'lib' );
        my %scripts       = map { $_ => catfile( 'blib', $_ ) } find( qr/(?:)/,   'script' );
        my %sdocs         = map { $_ => delete $scripts{$_} } grep {/.pod$/} keys %scripts;
        my %dist_shared   = map { $_ => catfile( qw[blib lib auto share dist],   $meta->name, abs2rel( $_, 'share' ) ) } find( qr/(?:)/, 'share' );
        my %module_shared = map { $_ => catfile( qw[blib lib auto share module], abs2rel( $_, 'module-share' ) ) } find( qr/(?:)/, 'module-share' );
        pm_to_blib( { %modules, %docs, %scripts, %dist_shared, %module_shared }, catdir(qw[blib lib auto]) );
        make_executable($_) for values %scripts;
        mkpath( catdir(qw[blib arch]), $verbose );
        0;
    }
    method step_clean() { rmtree( $_, $verbose ) for qw[blib temp]; 0 }

    method step_install() {
        $self->step_build() unless -d 'blib';
        install(
            [   from_to           => $install_paths->install_map,
                verbose           => $verbose,
                dry_run           => $dry_run,
                uninstall_shadows => $uninst,
                skip              => undef,
                always_copy       => 1
            ]
        );
        0;
    }
    method step_realclean () { rmtree( $_, $verbose ) for qw[blib temp Build _build_params MYMETA.yml MYMETA.json]; 0 }

    method step_test() {
        $self->step_build() unless -d 'blib';
        require TAP::Harness::Env;
        my %test_args = (
            ( verbosity => $verbose ),
            ( jobs  => $jobs ),
            ( color => -t STDOUT ),
            lib => [ map { rel2abs( catdir( 'blib', $_ ) ) } qw[arch lib] ],
        );
        TAP::Harness::Env->create( \%test_args )->runtests( sort map { $_->stringify } find( qr/\.t$/, 't' ) )->has_errors;
    }

    method _do_in_dir( $path, $sub ) {
        my $cwd = cwd()->absolute;
        chdir $path->absolute->stringify if -d $path->absolute;
        $sub->();
        chdir $cwd->stringify;
    }

    method step_build_libs() {
        my $pre = cwd->absolute->child( qw[blib arch auto], $meta->name );
        return 0 if -d $pre;
        my $p = $cwd->child('share')->realpath;
        if ( $^O eq 'MSWin32' && $prebuilt ) {
            say 'Using prebuilt SDL3...' if $verbose;
            next                         if $config{okay};
            my $store = tempdir()->child('SDL3.zip');
            my $okay  = $self->fetch( $archive, $store );
            die 'Failed to fetch SDL binaries' unless $okay;
            if ( $Config{cc} =~ m[gcc]i ) {
                my $platform = $Config{archname} =~ /x64/ ? 'x86_64-w64-mingw32' : 'i686-w64-mingw32';

                #~ $self->add_to_cleanup( $okay->canonpath );
                $okay->child($platform)->visit(
                    sub {
                        my ( $path, $state ) = @_;
                        $path->is_dir ? $p->child( $path->relative( $okay->child($platform) ) )->mkdir( { verbose => $verbose } ) :
                            $path->copy( $p->child( $path->relative( $okay->child($platform) ) ) );
                    },
                    { recurse => 1 }
                );
            }
            else {    # Assume VC
                my $platform = $Config{archname} =~ /x64/ ? 'x64' : 'x86';    # XXX - arm64 is untested, well so is VC right now...
                $okay->child('include')->visit(
                    sub {
                        my ( $path, $state ) = @_;
                        $path->is_dir ? $p->child( $path->relative( $okay->child('include') ) )->mkdir( { verbose => $verbose } ) :
                            $path->copy( $p->child( $path->relative( $okay->child('include') ) ) );
                    },
                    { recurse => 1 }
                );
                $okay->child( 'lib', $platform )->visit(
                    sub {
                        my ( $path, $state ) = @_;
                        $path->is_dir ? $p->child( $path->relative( $okay->child( 'lib', $platform ) ) )->mkdir( { verbose => $verbose } ) :
                            $path->copy( $p->child( $path->relative( $okay->child( 'lib', $platform ) ) ) );
                    },
                    { recurse => 1 }
                );
            }
            $config{type}    = 'share';
            $config{okay}    = 1;
            $config{version} = $version;
        }
        else {
            require DynaLoader;
            require Alien::cmake3;
            unshift @PATH, Alien::cmake3->bin_dir;
            say 'Looking for SDL3 library...' if $verbose;
            my ($path) = DynaLoader::dl_findfile('-lSDL3');
            if ($path) {
                $config{type} = 'system';
                $config{path} = path($path)->realpath->stringify;
                say 'Library found at ' . $config{path} if $verbose;
            }
            else {
                say 'Building SDL3 from source...' if $verbose;
                my $store = tempdir()->child( path($archive)->basename );
                my $build = tempdir()->child('build');
                my $okay  = $self->fetch( $archive, $store );
                die 'Failed to download SDL3 source' unless $okay;

                #~ $self->add_to_cleanup( $okay->canonpath );
                $config{path} = 'share';
                $config{okay} = 0;
                my $cflags = '';
                {
                    $self->_do_in_dir(
                        $okay,
                        sub {
                            system( Alien::cmake3->exe, grep {length} '-S ' . $okay,
                                '-B ' . $build->canonpath,      '--install-prefix=' . $p->canonpath,
                                '-Wdeprecated -Wdev -Werror',   '-DSDL_SHARED=ON',
                                '-DSDL_TESTS=OFF',              '-DSDL_INSTALL_TESTS=OFF',
                                '-DSDL_DISABLE_INSTALL_MAN=ON', '-DSDL_VENDOR_INFO=SDL3.pm',
                                '-DCMAKE_BUILD_TYPE=Release',   '-DSDL3_DIR=' . $cwd->child('share')->absolute,
                                $cflags
                            );
                            system( Alien::cmake3->exe, '--build', $build->canonpath

                                #, '--config Release', '--parallel'
                            );
                            die "Failed to build SDL3! %s\n", $archive // '' if system( Alien::cmake3->exe, '--install', $build->canonpath );
                            $config{okay}    = 1;
                            $config{version} = $version;
                        }
                    );
                }
            }
        }
        {
            my @out;
            push @out, sprintf '%s = %s', $_, $config{$_} for sort keys %config;
            $p->child('.config')->spew( join "\n", @out );
        }
    }

    method get_arguments (@sources) {
        $_ = detildefy($_) for grep {defined} $install_base, $destdir, $prefix, values %{$install_paths};
        $install_paths = ExtUtils::InstallPaths->new( dist_name => $meta->name );
        return;
    }

    method fetch ( $liburl, $outfile ) {
        $http //= HTTP::Tiny->new();
        printf 'Downloading %s... ', $liburl if $verbose;
        $outfile->parent->mkpath;
        my $response = $http->mirror( $liburl, $outfile, {} );
        say $response->{reason} if $verbose;
        if ( $response->{success} ) {    #ddx $response;

            #~ $self->add_to_cleanup($outfile);
            my $outdir = $outfile->parent->child( $outfile->basename( '.tar.gz', '.zip' ) );
            printf 'Extracting %s to %s... ', $outfile, $outdir if $verbose;
            require Archive::Extract;
            my $ae = Archive::Extract->new( archive => $outfile );
            if ( $ae->extract( to => $outdir ) ) {
                say 'done' if $verbose;

                #~ $self->add_to_cleanup( $ae->extract_path );
                return path( $ae->extract_path );
            }
            else {
                croak 'Failed to extract ' . $outfile;
            }
        }
        else {
            croak 'Failed to download ' . $liburl;
        }
        return 0;
    }

    method Build(@args) {
        my $method = $self->can( 'step_' . $action );
        $method // die "No such action '$action'\n";
        exit $method->($self);
    }

    method Build_PL() {
        say sprintf 'Creating new Build script for %s %s', $meta->name, $meta->version;
        $self->write_file( 'Build', sprintf <<'', $^X, __PACKAGE__, __PACKAGE__ );
#!%s
use lib 'builder';
use %s;
use Getopt::Long qw[GetOptionsFromArray];
my %%opts = ( @ARGV && $ARGV[0] =~ /\A\w+\z/ ? ( action => shift @ARGV ) : () );
GetOptionsFromArray \@ARGV, \%%opts, qw[install_base=s install_path=s%% installdirs=s destdir=s prefix=s config=s%% uninst:1 verbose:1 dry_run:1 jobs=i prebuilt:1];
%s->new(%%opts)->Build();

        make_executable('Build');
        my @env = defined $ENV{PERL_MB_OPT} ? split_like_shell( $ENV{PERL_MB_OPT} ) : ();
        $self->write_file( '_build_params', encode_json( [ \@env, \@ARGV ] ) );
        if ( my $dynamic = $meta->custom('x_dynamic_prereqs') ) {
            my %meta = ( %{ $meta->as_struct }, dynamic_config => 0 );
            $self->get_arguments( \@env, \@ARGV );
            require CPAN::Requirements::Dynamic;
            my $dynamic_parser = CPAN::Requirements::Dynamic->new();
            my $prereq         = $dynamic_parser->evaluate($dynamic);
            $meta{prereqs} = $meta->effective_prereqs->with_merged_prereqs($prereq)->as_string_hash;
            $meta = CPAN::Meta->new( \%meta );
        }
        $meta->save(@$_) for ['MYMETA.json'];
    }

    sub find ( $pattern, $base ) {
        $base = path($base) unless builtin::blessed $base;
        my $blah = $base->visit(
            sub ( $path, $state ) {
                $state->{$path} = $path if -f $path && $path =~ $pattern;

                #~ return \0 if keys %$state == 10;
            },
            { recurse => 1 }
        );
        values %$blah;
    }
    };
1;
