#!/bin/perl

use Modern::Perl;

use autodie qw(:all);
no indirect ':fatal';



#TODO:  1.  Add some form of support for Pre/Post init/cleardown condition generation.

use Carp;
use DBI;

use VSGDR::UnitTest::TestSet::Test;
use VSGDR::UnitTest::TestSet::Test::TestCondition;
use VSGDR::UnitTest::TestSet::Representation;
use VSGDR::UnitTest::TestSet::Resx;

use VSGDR::SQLServer::DataType;

use Readonly ;
use List::MoreUtils qw(any) ;

use File::Basename;
use Smart::Comments;
use Try::Tiny;


use Getopt::Euclid qw( :vars<opt_> );
use Data::Dumper;

use version ; our $VERSION = qv('1.2.8');

my $opt_scalarValues        = !$opt_noscalarValues;
my $opt_types               = !$opt_notypes;
my $gen_types               = 0;
$gen_types                  = (!$opt_notypes) if defined $opt_notypes ;

my $dataBase                = $opt_connection;
my $priv_dataBase           = undef ;
$priv_dataBase              = $opt_pconnection if defined $opt_pconnection ;


my @resultSets              = () ;
   @resultSets              = @opt_resultSets if defined @opt_resultSets ;
my $generateAllResultSets   = ! ( scalar @resultSets ) ;
my $generateScalarChecks    = $opt_scalarValues ;


my %Parsers = () ;
my %ValidParserMakeArgs = ( vb  => 'NET::VB'
                          , cs  => 'NET::CS'
                          , xls => 'XLS'
                          , xml => 'XML'
                          ) ;
my @validSuffixes       = map { '.'.$_ } keys %ValidParserMakeArgs ;

### Connect to database

my $dbh             = DBI->connect("dbi:ODBC:${dataBase}", q{}, q{}, { AutoCommit => 1, RaiseError => 1 });
my $dbh_typeinfo    = DBI->connect("dbi:ODBC:${dataBase}", q{}, q{}, { AutoCommit => 1, RaiseError => 1 });

# Always create a $priv_dbh handle, re-use the normal database dsn if no privileged dsn specified.
my $priv_dbh    = undef ;
if ( defined $priv_dataBase ) {
    $priv_dbh       = DBI->connect("dbi:ODBC:${priv_dataBase}", q{}, q{}, { AutoCommit => 1, RaiseError => 1 })
}
else {
    $priv_dbh       = DBI->connect("dbi:ODBC:${dataBase}", q{}, q{}, { AutoCommit => 1, RaiseError => 1 })
}


my $initSQL             = undef ;
my $cleardownSQL        = undef ;
my $PreTestSQL          = undef ;
my $PostTestSQL         = undef ;

$initSQL                = getFile($opt_initfile)                    if defined $opt_initfile ;
$cleardownSQL           = getFile($opt_cleanupfile)                 if defined $opt_cleanupfile ;
$PreTestSQL             = getFile($opt_prefile)                     if defined $opt_prefile ;
$PostTestSQL            = getFile($opt_postfile)                    if defined $opt_postfile ;

my $origInitSQL         = $initSQL ;
my $origCleardownSQL    = $cleardownSQL ;
my $origPreTestSQL      = $PreTestSQL ;
my $origPostTestSQL     = $PostTestSQL ;

$initSQL                = $dbh->quote($initSQL)                     if $initSQL ;
$cleardownSQL           = $dbh->quote($cleardownSQL)                if $cleardownSQL ;
$PreTestSQL             = $dbh->quote($PreTestSQL)                  if $PreTestSQL ;
$PostTestSQL            = $dbh->quote($PostTestSQL)                 if $PostTestSQL;

$initSQL                = 'exec sp_executesql N' . $initSQL         if $initSQL ;
$cleardownSQL           = 'exec sp_executesql N' . $cleardownSQL    if $cleardownSQL ;
$PreTestSQL             = 'exec sp_executesql N' . $PreTestSQL      if $PreTestSQL ;
$PostTestSQL            = 'exec sp_executesql N' . $PostTestSQL     if $PostTestSQL;


#exit ;

for ( my $i=0; $i <= $#opt_infile; $i++ ) {  ## Process SQL scripts:::                 done

    my $infile    = $opt_infile[$i];
    my($infname, $indirectories, $insfx)    = fileparse($infile, @validSuffixes);
    $insfx        = lc $insfx ;

    my $outfile   = $opt_outfile[$i];
    my($outfname, $outdirectories, $outsfx) = fileparse($outfile, @validSuffixes);
    $outsfx       = substr(lc $outsfx,1) ;

#warn Dumper $insfx;
#warn Dumper $outsfx;

    croak 'Invalid output file' unless exists $ValidParserMakeArgs{$outsfx} ;

    $Parsers{${outsfx}} = VSGDR::UnitTest::TestSet::Representation->make( { TYPE => $ValidParserMakeArgs{${outsfx}} } );

    my $testSet = VSGDR::UnitTest::TestSet->new( { NAMESPACE        => "${outfname}_ns"
                                                  , CLASSNAME        => "${outfname}_cls"
                                                  }
                                                ) ;


    $testSet->initializeAction($testSet->initializeActionLiteral())
        if $initSQL  ;
    $testSet->cleanupAction($testSet->cleanupActionLiteral())
        if $cleardownSQL  ;

    my $PreTestActionName   = 'null' ;
    my $PostTestActionName  = 'null' ;
    my $TestActionName      = "${outfname}_TestAction" ;

    $PreTestActionName      = "${outfname}_PreTestAction"   if $PreTestSQL ;
    $PostTestActionName     = "${outfname}_PostTestAction"  if $PostTestSQL ;

    my $test = VSGDR::UnitTest::TestSet::Test->new( { TESTNAME             => "${outfname}"
                                                     , TESTACTIONDATANAME   => "${outfname}Data"
                                                     , PRETESTACTION        => ${PreTestActionName}
                                                     , TESTACTION           => ${TestActionName}
                                                     , POSTTESTACTION       => ${PostTestActionName}
                                                     }
                                                   ) ;

### Open script files

    my $TestSQL     = getFile($infile)  ;
    my $origTestSQL = $TestSQL ;

    $TestSQL = $dbh->quote($TestSQL) ;
    $TestSQL = 'exec sp_executesql N' . $TestSQL ;

    if ( $initSQL ) {
### Run initialisation script
        my $p_sth = $priv_dbh->prepare($initSQL,{odbc_exec_direct => 1});
        $p_sth->execute();
    }
    if ( $PreTestSQL ) {
### Run pre-test script
        my $p_sth = $priv_dbh->prepare($PreTestSQL,{odbc_exec_direct => 1});
        $p_sth->execute();
    }

### Run test script once

    my @run1_res ;
    my @res_col ;
    my @res_type ;
    my $sth = $dbh->prepare($TestSQL,{odbc_exec_direct => 1});

    try {
        $sth->execute;
    } catch {
         #warn "caught error: $_\n";
         warn "File :- $infile\n";
         #warn "TRYING :- \n$TestSQL";
    };


    do {
        push @res_type, $sth->{TYPE} ;
        push @res_col,  $sth->{NAME} ;

        if ( $gen_types ) {
            {
            my @names   = map { scalar $dbh_typeinfo->type_info($_)->{TYPE_NAME} }   @{ $sth->{TYPE} } ;
            my @colSize = map { scalar $dbh_typeinfo->type_info($_)->{COLUMN_SIZE} } @{ $sth->{TYPE} } ;

            my @types = List::MoreUtils::pairwise { $a =~ m{char}ism ? "$a($b)" : "$a" }  @names, @colSize ;
            my @spec  = List::MoreUtils::pairwise { "$a\t\t\t$b" }  @{$sth->{NAME}}, @types ;

            do { local $"= "\n,\t" ;
                 say {*STDERR} "ResultSet(\n\t@{spec}\n)";
               }
            }
        }

        push @run1_res, $sth->fetchall_arrayref() ;

    } while ($sth->{odbc_more_results}) ;


    foreach my $row (@res_col) {
        for ( my $i = 0; $i < scalar (@$row) ; $i++ ) {
            my $y=$i+1;
            $$row[$i]  = "Col_${y}" if $$row[$i] eq q{} ;
        }
    }

### Run test script twice

    my @run2_res ;
    $sth = $dbh->prepare($TestSQL,{odbc_exec_direct => 1});
    $sth->execute;
    do {
        push @run2_res, $sth->fetchall_arrayref() ;

    } while ($sth->{odbc_more_results}) ;

    my @testConditions = () ;

    my $G_ln = 0 ;

### Build conditions

    my $conditionNamePrefix             = "${outfname}_Res_" ;

    Readonly::Scalar  my $testConditionTypeRC => 'RowCount' ;
    Readonly::Scalar  my $testConditionTypeSV => 'ScalarValue' ;

    for ( my $ra_arr = 0; $ra_arr < scalar (@res_col) ; $ra_arr++ ) {

        unless ( $generateAllResultSets ) {
            next unless any { $_ == ($ra_arr+1) } @resultSets ;
        }
    #warn Dumper @resultSets ;
    #warn "!!!!\n";
    #warn Dumper $ra_arr ;

        my $testConditionRC = VSGDR::UnitTest::TestSet::Test::TestCondition->make(
                    { TESTCONDITIONTYPE         => $testConditionTypeRC
                    , CONDITIONTESTACTIONNAME   => ${TestActionName}
                    , CONDITIONNAME             => ${conditionNamePrefix} . ($ra_arr+1) . "_RowCount"
                    , CONDITIONENABLED          => 'True'
                    , CONDITIONROWCOUNT         => scalar(@{$run1_res[$ra_arr]})
                    , CONDITIONRESULTSET        => $ra_arr+1
                    } ) ;
        push @testConditions, $testConditionRC ;

        my $single_row_output = ( scalar(@{$run1_res[$ra_arr]}) == 1
                                                           ? 1
                                                           : 0 );

        if ( $generateScalarChecks) {
            for ( my $ra_row = 0; $ra_row < scalar ( @{$run1_res[$ra_arr]} ) ; $ra_row++ ) {

                for ( my $ra_col = 0; $ra_col < scalar ( @{$res_col[$ra_arr]} ) ; $ra_col++ ) {

                    my $run1_dataValue = VSGDR::SQLServer::DataType->make( $res_type[$ra_arr][$ra_col]
                                                                              , $run1_res[$ra_arr][$ra_row][$ra_col]
                                                                              ) ;

                    my $run2_dataValue = VSGDR::SQLServer::DataType->make( $res_type[$ra_arr][$ra_col]
                                                                              , $run2_res[$ra_arr][$ra_row][$ra_col]
                                                                              ) ;

                    # check the values are stable from run to run before generating test condition.
                    if ( ( ( ! defined $run1_dataValue->value() ) and (! defined $run2_dataValue->value() ) ) or
                         ( (   defined $run1_dataValue->value()   and    defined $run2_dataValue->value() )
                                    and  $run1_dataValue eq $run2_dataValue
                         )
                       ) {
                        my $testConditionSV = VSGDR::UnitTest::TestSet::Test::TestCondition->make(
                                    { TESTCONDITIONTYPE         => $testConditionTypeSV
                                    , CONDITIONTESTACTIONNAME   => ${TestActionName}
                                    , CONDITIONNAME             => ( $single_row_output == 0
                                                                        ? ${conditionNamePrefix} . ($ra_arr+1) . '_Row_' . ($ra_row+1) . '_' . $res_col[$ra_arr][$ra_col]
                                                                        : ${conditionNamePrefix} . ($ra_arr+1) . '_' . $res_col[$ra_arr][$ra_col]
                                                                   )
                                    , CONDITIONENABLED          => 'True'
                                    , CONDITIONEXPECTEDVALUE    => ( defined $run1_dataValue->getValue()
                                                                        ? q{"} .    # " kill highlighting
                                                                        $run1_dataValue->quoteValue($run1_dataValue->getValue()) 
                                                                        . q{"}      # " kill highlighting
                                                                        : 'null'
                                                                   )
                                    , CONDITIONNULLEXPECTED     => defined $run1_res[$ra_arr][$ra_row][$ra_col] ? 'False' : 'True'
                                    , CONDITIONRESULTSET        => $ra_arr+1
                                    , CONDITIONROWNUMBER        => $ra_row+1
                                    , CONDITIONCOLUMNNUMBER     => $ra_col+1
                                    } ) ;

                        push @testConditions, $testConditionSV ;
                    }
                    else {
                        say {*STDERR} "Skipping mutating values for resultset\[${ra_arr}\], row\[${ra_row}\], column\[${ra_col}\] .." ;
                    }
                }
            }
        }
    }


    if ( $PostTestSQL ) {
### Run post-test script
        my $p_sth = $priv_dbh->prepare($PostTestSQL,{odbc_exec_direct => 1});
        $p_sth->execute();
    }

    if ( $cleardownSQL ) {
### Run cleardown script
        my $p_sth = $priv_dbh->prepare($cleardownSQL,{odbc_exec_direct => 1});
        $p_sth->execute();
    }


### Build GDR files

    $test->testAction(${TestActionName}) ;
    $test->test_conditions(\@testConditions) ;
    $testSet->tests([$test]) ;

    my $o_resx = VSGDR::UnitTest::TestSet::Resx->new() ;
    my %code = ( ${TestActionName} => $origTestSQL ) ;

    $code{$PreTestActionName}                   = $origPreTestSQL
        if $origPreTestSQL ;
    $code{$PostTestActionName}                  = $origPostTestSQL
        if $origPostTestSQL ;
    $code{$testSet->initializeActionLiteral()}  = $origInitSQL
        if $origInitSQL ;
    $code{$testSet->cleanupActionLiteral()}     = $origCleardownSQL
        if $origCleardownSQL ;


    $o_resx->scripts(\%code) ;
    $o_resx->serialise($outfname.'.resx',$o_resx) ;

    $Parsers{$outsfx}->serialise($opt_outfile[$i],$testSet);

}


exit ;

# #######################################################################################

sub getFile {
    local $_        = undef ;
    my $infile      = shift or croak 'no input filename' ;
    my $SQL         = q{} ;
    open my $infh, '<', $infile ;
    { local $/=undef ; $SQL = <$infh> ; close $infh ; } ;
    return scalar $SQL ;
}


END {
    $dbh->disconnect()          if $dbh ;
    $dbh_typeinfo->disconnect() if $dbh_typeinfo ;
    $priv_dbh->disconnect()     if $priv_dbh ;
}

__DATA__


=head1 NAME


genGDRTests.pl - Creates GDR test files from test sql scripts.
This version creates no test conditions for anything other than the main test file.
All files, other than the main test files are fixed, are the same for each generated test.
Test is run tw2ce to generate tests only for stable values. (Dates are still a problem.)

=head1 VERSION

1.2.8

=head1 USAGE

genGDRTests.pl -i <infile> -o <outfile> -c <odbc connection> -r <resultSets> [options]


=head1 REQUIRED ARGUMENTS

=over

=item  -i[n][file]   [=] <file>

Specify input file

=for Euclid:
    file.type:    readable
    repeatable


=item  -o[ut][file]  [=] <file>

Specify output file

=for Euclid:
    file.type:    writable
    repeatable

=item  -c[onnection] [=] <dsn>

Specify ODBC connection for Test script


=back



=head1 OPTIONS

=over

=item  -pc[onnection] [=] <dsn>

Specify privileged ODBC connection for Setup/Teardown scripts


=item  -pre[file]     [=] <prefile>

Pre-test file

=for Euclid:
    prefile.type:    readable

=item  -post[file]    [=] <postfile>

Post-test file

=for Euclid:
    postfile.type:    readable


=item  -init[file]    [=] <initfile>

Common initialisation code file

=for Euclid:
    initfile.type:    readable

=item  -cleanup[file] [=] <cleanupfile>

Common cleanup code file

=for Euclid:
    cleanupfile.type:    readable


=item  -r[esultSets]  [=] <resultSets>

Resultsets (numeric list) for which to generate test conditions

=for Euclid:
    resultSets.type:    int
    repeatable


=item  --[no]scalarValues

[Don't] generate scalar value test conditions

=for Euclid:
    false: --noscalarValues


=item  --[no]types

[Don't] generate SQL types declaration

=for Euclid:
    false: --notypes


=back


=head1 AUTHOR

Ded MedVed.



=head1 BUGS

Hopefully none.



=head1 COPYRIGHT

Copyright (c) 2012, Ded MedVed. All Rights Reserved.
This module is free software. It may be used, redistributed
and/or modified under the terms of the Perl Artistic License
(see http://www.perl.com/perl/misc/Artistic.html)

