#!perl -w                                         # -*- perl -*-
# vim:sw=4:ts=8
$|=1;

use strict;
use warnings;

use Cwd;
use Data::Dumper;
use Test::More;

use DBI qw(dbi_time);

if (my $ap = $ENV{DBI_AUTOPROXY}) { # limit the insanity
    plan skip_all => "transport+policy tests skipped with non-gofer DBI_AUTOPROXY"
        if $ap !~ /^dbi:Gofer/i;
    plan skip_all => "transport+policy tests skipped with non-pedantic policy in DBI_AUTOPROXY"
        if $ap !~ /policy=pedantic\b/i;
}

# 0=SQL::Statement if avail, 1=DBI::SQL::Nano
# next line forces use of Nano rather than default behaviour
$ENV{DBI_SQL_NANO}=1;

my $perf_count = (@ARGV && $ARGV[0] =~ s/^-c=//) ? shift : (-t STDOUT) ? 100 : 0;
my %durations;

# so users can try others from the command line
my $dbm = $ARGV[0];
if (!$dbm) {
    # pick first available, starting with SDBM_File
    for (qw( SDBM_File GDBM_File DB_File BerkeleyDB )) {
        if (eval { local $^W; require "$_.pm" }) {
            $dbm = ($_);
            last;
        }
    }
    plan skip_all => 'No DBM modules available' if !$dbm;
}
my $remote_driver_dsn = "dbm_type=$dbm;lockfile=0";
my $remote_dsn = "dbi:DBM:$remote_driver_dsn";
my $timeout = 10;

plan 'no_plan';

if ($ENV{DBI_AUTOPROXY}) {
    # this means we have DBD::Gofer => DBD::Gofer => DBD::DBM!
    # rather than disable it we let it run because we're twisted
    # and because it helps find more bugs (though debugging can be painful)
    warn "\n$0 is running with DBI_AUTOPROXY enabled ($ENV{DBI_AUTOPROXY})\n"
        unless $0 =~ /\bzv/; # don't warn for t/zvg_85gofer.t
}

# ensure subprocess (for pipeone and stream transport) will use the same modules as us, ie ./blib
local $ENV{PERL5LIB} = join ":", @INC;


my $getcwd = getcwd();
my $username = eval { getpwuid($>) } || ''; # fails on windows
my $can_ssh = ($username && $username eq 'timbo' && -d '.svn');
my $perl = "$^X  -Mblib=$getcwd/blib"; # ensure sameperl and our blib (note two spaces)

my %trials = (
    null       => {},
    pipeone    => { perl=>$perl, timeout=>$timeout },
    stream     => { perl=>$perl, timeout=>$timeout },
    stream_ssh => ($can_ssh)
                ? { perl=>$perl, timeout=>$timeout, url => "ssh:$username\@localhost" }
                : undef,
    #http       => { url => "http://localhost:8001/gofer" },
);

# too dependant on local config to make a standard test
delete $trials{http} unless $username eq 'timbo' && -d '.svn';

for my $trial (sort keys %trials) {
    (my $transport = $trial) =~ s/_.*//;
    my $trans_attr = $trials{$trial}
        or next;

    # XXX temporary restrictions, hopefully
    if ($^O eq 'MSWin32') {
       # stream needs Fcntl macro F_GETFL for non-blocking
       # and pipe seems to hang on some windows systems
        next if $transport eq 'stream' or $transport eq 'pipeone';
    }

    for my $policy_name (qw(pedantic classic rush)) {

        eval { run_tests($transport, $trans_attr, $policy_name) };
        ($@) ? fail("$trial: $@") : pass();

    }
}

# to get baseline for comparisons if doing performance testing
run_tests('no', {}, 'pedantic') if $perf_count;

while ( my ($activity, $stats_hash) = each %durations ) {
    print "\n";
    $stats_hash->{'~baseline~'} = delete $stats_hash->{"no+pedantic"};
    for my $perf_tag (reverse sort keys %$stats_hash) {
        my $dur = $stats_hash->{$perf_tag} || 0.0000001;
        printf "  %6s %-16s: %.6fsec (%5d/sec)",
            $activity, $perf_tag, $dur/$perf_count, $perf_count/$dur;
        my $baseline_dur = $stats_hash->{'~baseline~'};
        printf " %+5.1fms", (($dur-$baseline_dur)/$perf_count)*1000
            unless $perf_tag eq '~baseline~';
        print "\n";
    }
}


sub run_tests {
    my ($transport, $trans_attr, $policy_name) = @_;

    my $policy = get_policy($policy_name);

    my $test_run_tag = "Testing $transport transport with $policy_name policy";
    print "\n$test_run_tag\n";

    my $driver_dsn = "transport=$transport;policy=$policy_name";
    $driver_dsn .= join ";", '', map { "$_=$trans_attr->{$_}" } keys %$trans_attr
        if %$trans_attr;

    my $dsn = "dbi:Gofer:$driver_dsn;dsn=$remote_dsn";
    $dsn = $remote_dsn if $transport eq 'no';
    print " $dsn\n";

    my $dbh = DBI->connect($dsn, undef, undef, { RaiseError => 1, PrintError => 0, ShowErrorStatement => 1 } );
    die "$test_run_tag aborted: $DBI::errstr\n" unless $dbh; # no point continuing
    ok $dbh, sprintf "should connect to %s", $dsn;

    is $dbh->{Name}, ($policy->skip_connect_check)
        ? $driver_dsn
        : $remote_driver_dsn;

    ok $dbh->do("DROP TABLE IF EXISTS fruit");
    ok $dbh->do("CREATE TABLE fruit (dKey INT, dVal VARCHAR(10))");
    die "$test_run_tag aborted\n" if $DBI::err;

    my $sth = do {
        local $dbh->{RaiseError} = 0;
        $dbh->prepare("complete non-sql gibberish");
    };
    ($policy->skip_prepare_check)
        ? isa_ok $sth, 'DBI::st'
        : is $sth, undef, 'should detect prepare failure';

    ok my $ins_sth = $dbh->prepare("INSERT INTO fruit VALUES (?,?)");
    ok $ins_sth->execute(1, 'oranges');
    ok $ins_sth->execute(2, 'oranges');

    my $rowset;
    ok $rowset = $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit ORDER BY dKey");
    is_deeply($rowset, [ [ '1', 'oranges' ], [ '2', 'oranges' ] ]);

    ok $dbh->do("UPDATE fruit SET dVal='apples' WHERE dVal='oranges'");
    ok $dbh->{go_response}->executed_flag_set, 'go_response executed flag should be true';

    ok $sth = $dbh->prepare("SELECT dKey, dVal FROM fruit");
    ok $sth->execute;
    ok $rowset = $sth->fetchall_hashref('dKey');
    is_deeply($rowset, { '1' => { dKey=>1, dVal=>'apples' }, 2 => { dKey=>2, dVal=>'apples' } });

    if ($perf_count and $transport ne 'pipeone') {
        print "performance check - $perf_count selects and inserts\n";
        my $start = dbi_time();
        $dbh->selectall_arrayref("SELECT dKey, dVal FROM fruit")
            for (1000..1000+$perf_count);
        $durations{select}{"$transport+$policy_name"} = dbi_time() - $start;

        # some rows in to get a (*very* rough) idea of overheads
        $start = dbi_time();
        $ins_sth->execute($_, 'speed')
            for (1000..1000+$perf_count);
        $durations{insert}{"$transport+$policy_name"} = dbi_time() - $start;
    }

    my $skip_go_request_count_check = ($transport eq 'no');

    print "Testing go_request_count and caching of simple values\n";
    my $go_request_count = $dbh->{go_request_count};
    ok $go_request_count
        unless $skip_go_request_count_check && pass();

    ok $dbh->do("DROP TABLE fruit");
    is ++$go_request_count, $dbh->{go_request_count}
        unless $skip_go_request_count_check && pass();

    # tests go_request_count, caching, and skip_default_methods policy
    my $use_remote = ($policy->skip_default_methods) ? 0 : 1;
    printf "use_remote=%s (policy=%s, transport=%s) %s\n",
        $use_remote, $policy_name, $transport, $dbh->{dbi_default_methods}||'';

SKIP: {
    skip "skip_default_methods checking doesn't work with Gofer over Gofer", 3
        if $ENV{DBI_AUTOPROXY} or $skip_go_request_count_check;
    $dbh->data_sources({ foo_bar => $go_request_count });
    is $dbh->{go_request_count}, $go_request_count + 1*$use_remote;
    $dbh->data_sources({ foo_bar => $go_request_count }); # should use cache
    is $dbh->{go_request_count}, $go_request_count + 1*$use_remote;
    @_=$dbh->data_sources({ foo_bar => $go_request_count }); # no cached yet due to wantarray
    is $dbh->{go_request_count}, $go_request_count + 2*$use_remote;
}

SKIP: {
    skip "caching of metadata methods returning sth not yet implemented", 2;
    print "Testing go_request_count and caching of sth\n";
    $go_request_count = $dbh->{go_request_count};
    my $sth_ti1 = $dbh->table_info("%", "%", "%", "TABLE", { foo_bar => $go_request_count });
    is $go_request_count + 1, $dbh->{go_request_count};

    my $sth_ti2 = $dbh->table_info("%", "%", "%", "TABLE", { foo_bar => $go_request_count }); # should use cache
    is $go_request_count + 1, $dbh->{go_request_count};
}

    ok $dbh->disconnect;
}

sub get_policy {
    my ($policy_class) = @_;
    $policy_class = "DBD::Gofer::Policy::$policy_class" unless $policy_class =~ /::/;
    _load_class($policy_class) or die $@;
    return $policy_class->new();
}

sub _load_class { # return true or false+$@
    my $class = shift;
    (my $pm = $class) =~ s{::}{/}g;
    $pm .= ".pm"; 
    return 1 if eval { require $pm };
    delete $INC{$pm}; # shouldn't be needed (perl bug?) and assigning undef isn't enough
    undef; # error in $@
}   


1;
