# A debhelper build system class for handling pkg-js-autopkgtest test.
#
# Copyright: 2019, Xavier Guimard <yadd@debian.org>
# License: GPL-2+

package Debian::Debhelper::Buildsystem::nodejs;

use strict;
use warnings;
use Debian::Debhelper::Dh_Lib
  qw(doit_noerror dpkg_architecture_value package_is_arch_all getpackages);
use File::Find;
use JSON;
use parent qw(Debian::Debhelper::Buildsystem);

sub DESCRIPTION {
    "pkg-js-tools test";
}

my $fileIgnored =
  qr/^(?:changelog|license|history|copying|contribute|\..*|.*\.md)$/i;

sub new {
    my $class = shift;
    return $class->SUPER::new(@_);
}

my %json;

# package.json cache
sub pjson {
    my ( $self, $dir ) = @_;
    unless ( -e "$dir/package.json" ) {
        print STDERR "/!\\ $dir/package.json not found\n";
        return undef;
    }
    return $json{$dir} if $json{$dir};
    my $pkgjson;
    open $pkgjson, "$dir/package.json";
    my $content = join '', <$pkgjson>;
    close $pkgjson;
    return $json{$dir} = JSON::from_json($content);
}

sub component_list {
    my $self = shift;
    my ( @components, %packages );
    if ( -e 'debian/watch' ) {
        my $watch;
        open $watch, 'debian/watch';
        while (<$watch>) {
            next if /^\s*#/;
            push @components, $1 if (/component=([\w\-\.]+)/);
        }
        close $watch;
    }
    foreach my $component (@components) {
        if ( -d $component ) {
            my $package;
            eval { $package = $self->pjson($component)->{name} };
            next if ($@);
            $packages{$component} = $package;
        }
    }
    return \%packages;
}

# Public methods
# --------------

sub check_auto_buildable {
    my $self = shift;
    if ( -e 'package.json' ) {
        return 1;
    }
    return 0;
}

# auto_configure step: if component are found, create node_modules links
sub configure {
    my $self       = shift;
    my $components = $self->component_list;
    my @dirs       = ( sort keys %$components );
    return unless @dirs;
    $self->doit_in_builddir( 'mkdir', 'node_modules' ) unless -e 'node_modules';

    # Link each component in node_modules/
    foreach (@dirs) {
        $self->doit_in_builddir( 'mkdir', "$_/node_modules" )
          unless -e "$_/node_modules";
    }

    # Link each component in <other-components>/node_modules
    foreach my $component (@dirs) {
        my $package = $components->{$component};
        $self->doit_in_builddir( 'ln', '-s', "../$component",
            "node_modules/$package" );
        foreach my $dir (@dirs) {
            next if $dir eq $component;
            $self->doit_in_builddir( 'mkdir', "$component/node_modules" )
              unless -e "$component/node_modules";
            $self->doit_in_builddir(
                'ln',               '-s',
                "../../$component", "$dir/node_modules/$package"
            ) unless -e "$component/node_modules/$package";
        }
    }
}

# test step: simple require or real test if declared
sub test {
    my $self = shift;
    if ( -e 'debian/tests/pkg-js/test' ) {
        $self->doit_in_builddir( '/bin/sh', '-e', 'debian/tests/pkg-js/test' );
    }
    else {
        $self->doit_in_builddir( '/usr/bin/node', '-e', 'require(".")' );
    }
}

# install step:
#  - if no debian/install file found, install automatically module
#  - install components
sub install {
    my $self    = shift;
    my $destdir = shift;
    my @pkgs    = getpackages();
    my ($package) = $#pkgs ? ( grep { $_ =~ /^node-/ } getpackages() ) : @pkgs;
    ($package) = @pkgs unless ($package);
    my $arch_all    = package_is_arch_all($package);
    my $node_module = $self->pjson('.')->{name};
    my $components  = $self->component_list;
    my @dirs        = ( sort keys %$components );

    # main install
    my $path = "$destdir/"
      . (
        $arch_all
        ? '/usr/share/nodejs'
        : '/usr/lib/'
          . dpkg_architecture_value("DEB_HOST_GNU_TYPE")
          . '/nodejs'
      ) . "/$node_module";
    if (   -e 'debian/install'
        or -e "debian/$package.install" )
    {
        print "Install file found, skipping auto install for main module\n";
    }
    else {
        $self->doit_in_builddir( 'mkdir', '-p', "$path" );
        $self->install_module( $path, '.', @dirs );
    }

    # Component install
    return unless (@dirs);
    $path = "$path/node_modules";
    if ( -e 'debian/node_submodules' ) {
        open my $f, 'debian/node_submodules';
        my @d = map { chomp; /^\w/ ? $_ : () } <$f>;
        close $f;
        unless (@d) {
            print
"Components auto-install is skipped by empty debian/node_submodules";
            return;
        }
        my @tmp;
      CMP: foreach my $cmp (@d) {
            foreach my $dir (@dirs) {
                if ( $dir eq $cmp or $components->{$dir} eq $cmp ) {
                    push @tmp, $dir;
                    next CMP;
                }
            }
            die qq{In debian/node_submodules, "$cmp" matches nothing};
        }
        @dirs = @tmp;
    }
    $self->doit_in_builddir( 'mkdir', '-p', "$path" );
    foreach my $dir (@dirs) {
        $self->install_module( "$path/$components->{$dir}", $dir );
    }
}

# Private method to install modules: search for package.json#files field, if
# not found, install any files except ignored by regexp
sub install_module {
    my ( $self, $path, $dir, @excludedDirs ) = @_;
    my $re = (
        @excludedDirs
        ? '^(?:' . join( '|', @excludedDirs ) . ')(?:/.*)?$'
        : '##'
    );
    $re = qr/$re/;

    # If "files" field exists, only its paths will be examined
    my @files;
    my $noFilesField = 1;
    if ( $self->pjson($dir)->{files} ) {
        print qq{Found "files" field in $dir/package.json, using it\n};
        @files = map { "$dir/$_" } @{ $self->pjson($dir)->{files} };
        push @files, "$dir/package.json"
          unless grep { $_ eq 'package.json' } @files;
        $noFilesField = 0;
    }
    else {
        print qq{No "files" field in $dir/package.json, install all files\n};
        @files = ($dir);
    }
    my @dest;
    foreach my $p (@files) {
        find(
            sub {
                my $d = $File::Find::dir;
                $d =~ s#^$dir/?##;
                unless (
                    ( -d $_ )

                    # Don't parse components sub dir for main module
                    or ( $d and $d =~ $re )

                    # Ignore license, changelog, readme,... files
                    or ( $_ =~ $fileIgnored )

                    # Ignore hidden directories
                    or ( $File::Find::dir =~ m{/\.} )

                    # Ignore our debian/ dir
                    or ( $File::Find::dir =~ m{^(?:./)?debian} )

                  # Ignore test directory unless specified in package.json#files
                    or (    $d
                        and $d =~ m{^(?:(?:doc|example|test)s)?(?:/.*)?$}i
                        and $noFilesField )

                    # Ignore test.js file unless specified in package.json#files
                    or (    $File::Find::dir eq $dir
                        and /^(?:example|test)s?\.js$/i
                        and $noFilesField )
                  )
                {
                    push @dest, [ $d, $File::Find::name ];
                }

                #else {
                #    print STDERR
                #      "$File::Find::name skipped\n $File::Find::dir\n $d\n";
                #    print STDERR "test1\n" if ( -d $_ );
                #    print STDERR "test2\n" if ( $d and $d =~ $re );
                #    print STDERR "test3\n" if $File::Find::dir =~ m{/\.};
                #    print STDERR "test4\n"
                #      if $File::Find::dir =~ m{^(?:./)?debian};
                #    print STDERR "test5\n"
                #      if $d
                #      and $d =~ m{^(?:(?:doc|example|test)s)?(?:/.*)?$}i
                #      and $noFilesField;
                #    print STDERR "test6\n"
                #      if $File::Find::dir eq $dir
                #      and /^(?:example|test)s?\.js$/i
                #      and $noFilesField;
                #}
            },
            $p
        );
    }
    foreach (@dest) {
        $self->doit_in_builddir( 'mkdir', '-p', "$path/$_->[0]" )
          unless doit_noerror( { chdir => $self->get_buildpath },
            'test', '-e', "$path/$_->[0]" );
        my $mode = ( -x $_->[1] ? '755' : '644' );
        $self->doit_in_builddir( 'install', '-m', $mode, $_->[1],
            "$path/$_->[0]/" );
    }
}

# clean step: try to clean node_modules directories
sub clean {
    my $self       = shift;
    my $components = $self->component_list;
    my @dirs       = ( sort keys %$components );
    foreach my $component (@dirs) {
        my $package = $components->{$component};
        unlink "node_modules/$package";
        foreach my $dir (@dirs) {
            next if $dir eq $component;
            unlink "$dir/node_modules/$package";
        }
    }
    rmdir "$_/node_modules" foreach (@dirs);
    rmdir 'node_modules';
}

1;
