use strict;
use warnings;
use lib 't/lib';
use Test::More 'no_plan';
use ExtUtils::MakeMaker::Config;
use File::Spec;
use Cwd;
use File::Temp qw[tempdir];

use ExtUtils::MakeMaker;
use MakeMaker::Test::Utils;

# Liblist wants to be an object which has File::Spec capabilities, so we
# mock one.

BEGIN {
    package MockEUMM;
    use base 'File::Spec';    # what.
    sub new { return bless {}, 'MockEUMM'; }
    sub lsdir { # cut'n'paste from MM_Unix
        #  $self
        my(undef, $dir, $regex) = @_;
        opendir(my $dh, defined($dir) ? $dir : ".")
            or return;
        my @ls = readdir $dh;
        closedir $dh;
        @ls = grep(/$regex/, @ls) if defined $regex;
        @ls;
    }
}

# similar to dispatching in EU::LL::Kid
my $OS = $^O eq 'MSWin32' ? 'win32' : ($^O eq 'VMS' ? 'vms' : 'unix_os2');

use_ok( 'ExtUtils::Liblist::Kid' );
move_to_os_test_data_dir();
conf_reset();
test_common();
test_kid_unix_os2() if $OS eq 'unix_os2';
test_kid_win32() if $OS eq 'win32';

# This allows us to get a clean playing field and ensure that the current
# system configuration does not affect the test results.

sub conf_reset {
    my @save_keys = qw{ so dlsrc osname make };
    my %save_config;
    @save_config{ @save_keys } = @Config{ @save_keys };
    %Config = %save_config;
    # The following are all used and always are defined in the real world.
    # Define them to something here to avoid spewing uninitialized value warnings.
    $Config{installarchlib} = 'lib';
    $Config{perllibs} = ''; # else on Windows :nosearch gives long extra list
    if ($^O eq 'VMS') {
        $Config{ldflags}     = '';
        $Config{dbgprefix}   = '';
        $Config{libc}        = '';
        $Config{ext_ext}     = '';
        $Config{lib_ext}     = '';
        $Config{obj_ext}     = '';
        $Config{so}          = '';
        $Config{vms_cc_type} = '';
        $Config{libpth}      = '';
    }
    delete $ENV{LIB};
    delete $ENV{LIBRARY_PATH};
    return;
}

# This keeps the directory paths in the tests short and allows easy
# separation of OS-specific files.

my $cwd;
sub move_to_os_test_data_dir {
    my %os_test_dirs = (
        win32 => {
            '__test.lib' => '',
            'di r/dir_test.lib' => '',
            'dir/dir_test.lib' => '',
            'double.lib' => '',
            'imp.dll.a' => '',
            'lib/CORE/c_test.lib' => '',
            'lib/CORE/double.lib' => '',
            'lib__test.lib' => '',
            'lib_test.lib' => '',
            'libpath/lp_test.lib' => '',
            'pl.lib' => '',
            'space lib.lib' => '',
            'test.a.lib' => '',
            'test.lib' => '',
            'test.meep' => '',
            'test2.lib' => '',
            'vc/vctest.lib' => '',
        },
        unix_os2 => {
            "libfoo.$Config{so}" => '',
            "di r/libdir_test.$Config{so}" => '',
        },
    );
    $cwd = getcwd; END { chdir $cwd } # so File::Temp can cleanup
    return if !$os_test_dirs{$OS};
    my $new_dir = tempdir( DIR => 't', CLEANUP => 1 );
    hash2files($new_dir, $os_test_dirs{$OS});
    chdir $new_dir or die "Could not change to liblist test dir '$new_dir': $!";
}

# Since liblist is object-based, we need to provide a mock object.
sub _ext { ExtUtils::Liblist::Kid::ext( MockEUMM->new, @_ ); }

sub quote { join ' ', map { qq{"$_"} } @_ }
sub double { (@_) x 2 }

# tests go here

sub test_common {
    my @expected = ('','','','');
    $expected[2] = 'PerlShr/Share' if $^O eq 'VMS';
    my $warnings = "";
    local $SIG{__WARN__} = sub { $warnings .= "@_\n"; };
    is_deeply( [ _ext() ], \@expected, 'empty input results in empty output' );
    is_deeply( [ _ext( 'unreal_test' ) ], \@expected, 'non-existent file results in empty output' );
    push @expected, [];
    is_deeply( [ _ext( undef, 0, 1 ) ], \@expected, 'asking for real names with empty input results in an empty extra array' );
    is_deeply( [ _ext( 'unreal_test',     0, 1 ) ], \@expected, 'asking for real names with non-existent file results in an empty extra array' );
}

sub test_kid_unix_os2 {
    my $warnings = "";
    local $SIG{__WARN__} = sub { $warnings .= "@_\n"; };
    my @out = _ext( '-L. -lfoo' );
    my $qlibre = qr/-L[^"]+\s+-lfoo/;
    like( $out[0], $qlibre, 'existing file results in quoted extralibs' );
    like( $out[2], $qlibre, 'existing file results in quoted ldloadlibs' );
    ok $out[3], 'existing file results in true LD_RUN_PATH';
    is_deeply [ _ext( '-L. -lnotthere' ) ], [ ('') x 4 ], 'non-present lib = empty';
    my $curr_dirspace = File::Spec->rel2abs( 'di r' );
    my $cmd_frag = '-L'.quote($curr_dirspace) . ' -ldir_test';
    is_deeply [ _ext( '-L"di r" -ldir_test' ) ], [ $cmd_frag, '', $cmd_frag, $curr_dirspace ], '-L directories with spaces work';
    {
      local $Config{libpth} = 'di r';
      my @got = _ext( '-ldir_test' );
      is_deeply \@got, [ '-ldir_test', '', '-ldir_test', 'di r' ], 'Config.libpth directories with spaces work' or diag explain \@got;
    }
    my $mm = WriteMakefile(
        NAME            => 'Big::Dummy',
        VERSION    => '1.00',
        LIBS => ['-L. -lfoo'],
    );
    like $mm->{LDLOADLIBS}, $qlibre, 'single LIBS works';
    $mm = WriteMakefile(
        NAME            => 'Big::Dummy',
        VERSION    => '1.00',
        LIBS => ['-L. -lnotthere', '-L. -lfoo'],
    );
    like $mm->{LDLOADLIBS}, $qlibre, 'two LIBS correctly gets second';
}

sub test_kid_win32 {
    my $warnings = "";
    local $SIG{__WARN__} = sub { $warnings .= "@_\n"; };
    my @got32 = _ext( 'kernel32' );
    like $got32[0], qr/kernel32/, 'found Win32 system library' or diag 'got: ', explain \@got32;
    is_deeply( [ _ext( 'test' ) ], [ double(quote('test.lib'), '') ], 'existent file results in a path to the file. .lib is default extension with empty %Config' );
    is_deeply( [ _ext( 'c_test' ) ], [ double(quote('lib\CORE\c_test.lib'), '') ], '$Config{installarchlib}/CORE is the default search dir aside from cwd' );
    is_deeply( [ _ext( 'double' ) ], [ double(quote('double.lib'), '') ], 'once an instance of a lib is found, the search stops' );
    is_deeply( [ _ext( 'test.lib' ) ], [ double(quote('test.lib'), '') ], 'the extension is not tacked on twice' );
    is_deeply( [ _ext( 'test.a' ) ], [ double(quote('test.a.lib'), '') ], 'but it will be tacked onto filenames with other kinds of library extension' );
    is_deeply( [ _ext( 'test test2' ) ], [ double(quote(qw(test.lib test2.lib)), '') ], 'multiple existing files end up separated by spaces' );
    is_deeply( [ _ext( 'test test2 unreal_test' ) ], [ double(quote(qw(test.lib test2.lib)),  '') ], "some existing files don't cause false positives" );
    is_deeply( [ _ext( '-l_test' ) ], [ double(quote('lib_test.lib'), '') ], 'prefixing a lib with -l triggers a second search with prefix "lib" when gcc is not in use' );
    is_deeply( [ _ext( '-l__test' ) ], [ double(quote('__test.lib'), '') ], 'unprefixed lib files are found first when -l is used' );
    is_deeply( [ _ext( '-llibtest' ) ], [ ('') x 4 ], 'if -l is used and the lib name is already prefixed no second search without the prefix is done' );
    is_deeply( [ _ext( '-lunreal_test' ) ], [ ('') x 4 ], 'searching with -l for a non-existent library does not cause an endless loop' );
    is_deeply( [ _ext( '"space lib"' ) ], [ double(quote('space lib.lib'), '') ], 'lib with spaces in the name can be found with the help of quotes' );
    is_deeply( [ _ext( '"""space lib"""' ) ],        [ double(quote('space lib.lib'), '') ], 'Text::Parsewords deals with extraneous quotes' );

    is_deeply( [ scalar _ext( 'test' ) ], [quote('test.lib')], 'asking for a scalar gives a single string' );

    is_deeply( [ _ext( 'c_test', 0, 1 ) ], [ double(quote('lib\CORE\c_test.lib'), ''), [quote('lib/CORE\c_test.lib')] ], 'asking for real names with an existent file in search dir results in an extra array with a mixed-os file path?!' );
    is_deeply( [ _ext( 'test c_test',     0, 1 ) ], [ double(quote(qw(test.lib lib\CORE\c_test.lib)), ''), [quote('lib/CORE\c_test.lib')] ], 'files in cwd do not appear in the real name list?!' );
    is_deeply( [ _ext( '-lc_test c_test', 0, 1 ) ], [ double(quote(qw(lib\CORE\c_test.lib lib\CORE\c_test.lib)), ''), [quote('lib/CORE\c_test.lib')] ], 'finding the same lib in a search dir both with and without -l results in a single listing in the array' );

    is_deeply( [ _ext( 'test :nosearch unreal_test test2' ) ], [ double(quote(qw(test.lib unreal_test test2)), '') ], ':nosearch can force passing through of filenames as they are' );
    is_deeply( [ _ext( 'test :nosearch -lunreal_test test2' ) ],       [ double(quote(qw(test.lib unreal_test.lib test2)), '') ], 'lib names with -l after a :nosearch are suffixed with .lib and the -l is removed' );
    is_deeply( [ _ext( 'test :nosearch unreal_test :search test2' ) ], [ double(quote(qw(test.lib unreal_test test2.lib)), '') ], ':search enables file searching again' );
    is_deeply( [ _ext( 'test :meep test2' ) ], [ double(quote(qw(test.lib test2.lib)), '') ], 'unknown :flags are safely ignored' );

    my $curr = File::Spec->rel2abs( '' );
    is_deeply( [ _ext( qq{"-L$curr/dir" dir_test} ) ], [ double(quote("$curr\\dir\\dir_test.lib"), '') ], 'directories in -L parameters are searched' );
    is_deeply( [ _ext( "-L/non_dir dir_test" ) ], [ ('') x 4 ], 'non-existent -L dirs are ignored safely' );
    is_deeply( [ _ext( qq{"-Ldir" dir_test} ) ], [ double(quote("$curr\\dir\\dir_test.lib"), '') ], 'relative -L directories work' );
    is_deeply( [ _ext( '-L"di r" dir_test' ) ], [ double(quote($curr . '\di r\dir_test.lib'), '') ], '-L directories with spaces work' );

    {
      local $Config{perllibs} = 'pl';
      is_deeply( [ _ext( 'unreal_test' ) ], [ ('') x 4 ], '_ext not add $Config{perllibs}' );
      is_deeply( [ _ext( 'unreal_test :nodefault' ) ], [ ('') x 4 ], ':nodefault flag prevents $Config{perllibs} from being added' );
      my $mm = WriteMakefile(
          NAME            => 'Big::Dummy',
          VERSION    => '1.00',
          LIBS => ['-l_test'],
      );
      my $exp = '"lib_test.lib" "pl.lib"';
      is $mm->{LDLOADLIBS}, $exp, 'single LIBS works';
      $mm = WriteMakefile(
          NAME            => 'Big::Dummy',
          VERSION    => '1.00',
          LIBS => ['-L. -lnotthere', '-l_test'],
      );
      is $mm->{LDLOADLIBS}, $exp, 'two LIBS correctly gets second';
    }

    {
      local $Config{libpth} = 'libpath';
      is_deeply( [ _ext( 'lp_test' ) ], [ double(quote('libpath\lp_test.lib'), '') ], '$Config{libpth} adds extra search paths' );
      $Config{libpth} = 'di r';
      my @got = _ext( '-ldir_test' );
      is_deeply \@got, [double('"di r\\dir_test.lib"', '')], 'Config.libpth directories with spaces work' or diag explain \@got;
    }

    $Config{lib_ext} = '.meep';
    is_deeply( [ _ext( 'test' ) ], [ double(quote('test.meep'), '') ], '$Config{lib_ext} changes the lib extension to be searched for' );
    delete $Config{lib_ext};

    $Config{lib_ext} = '.a';
    is_deeply( [ _ext( 'imp' ) ], [ double(quote('imp.dll.a'), '') ], '$Config{lib_ext} == ".a" will find *.dll.a too' );
    delete $Config{lib_ext};

    $Config{cc} = 'C:/MinGW/bin/gcc.exe';

    is_deeply( [ _ext( 'test' ) ], [ double(quote('test.lib'), '') ], '[gcc] searching for straight lib names remains unchanged' );
    is_deeply( [ _ext( '-l__test' ) ], [ double(quote('lib__test.lib'), '') ], '[gcc] lib-prefixed library files are found first when -l is in use' );
    is_deeply( [ _ext( '-ltest' ) ], [ double(quote('test.lib'), '') ], '[gcc] non-lib-prefixed library files are found on the second search when -l is in use' );
    is_deeply( [ _ext( '-llibtest' ) ], [ double(quote('test.lib'), '') ], '[gcc] if -l is used and the lib name is already prefixed a second search without the lib is done' );
    is_deeply( [ _ext( ':nosearch -lunreal_test' ) ], [ double(quote('-lunreal_test'), '') ], '[gcc] lib names with -l after a :nosearch remain as they are' );

    $ENV{LIBRARY_PATH} = 'libpath';
    is_deeply( [ _ext( 'lp_test' ) ], [ double(quote('libpath\lp_test.lib'), '') ], '[gcc] $ENV{LIBRARY_PATH} adds extra search paths' );
    delete $ENV{LIBRARY_PATH};

    $Config{cc} = 'c:/Programme/Microsoft Visual Studio 9.0/VC/bin/cl.exe';

    is_deeply( [ _ext( 'test' ) ], [ double(quote('test.lib'), '') ], '[vc] searching for straight lib names remains unchanged' );
    is_deeply( [ _ext( ':nosearch -Lunreal_test' ) ], [ double(quote('-libpath:unreal_test'), '') ], '[vc] lib dirs with -L after a :nosearch are prefixed with -libpath:' );
    ok( !exists $ENV{LIB}, '[vc] $ENV{LIB} is not autovivified' );

    $ENV{LIB} = 'vc';
    is_deeply( [ _ext( 'vctest.lib' ) ], [ double(quote('vc\vctest.lib'), '') ], '[vc] $ENV{LIB} adds search paths' );
}