#!/usr/bin/perl

# suppress 'WEXRP00: Found multiple rperl executables' due to blib/ & pre-existing installation(s),
#BEGIN { $ENV{RPERL_WARNINGS} = 0; }

# [[[ HEADER ]]]
#use RPerl;  # disabled here & RPERL_WARNINGS above & RPerl::diag() below, only re-enable for RPerl::diag() debugging use
use strict;
use warnings;
our $VERSION = 0.006_000;

# [[[ CRITICS ]]]
## no critic qw(ProhibitUselessNoCritic ProhibitMagicNumbers RequireCheckedSyscalls)  # USER DEFAULT 1: allow numeric values & print operator
## no critic qw(ProhibitStringyEval)  # SYSTEM DEFAULT 1: allow eval()

# [[[ INCLUDES ]]]
use Test::More tests => 16;
use Test::Exception;
use Test::Number::Delta;
use Carp;
use English qw(-no_match_vars);

# [[[ OPERATIONS ]]]

BEGIN {
    if ( $ENV{RPERL_VERBOSE} ) {
        Test::More::diag('[[[ Beginning Inline::CPP Pre-Test Loading ]]]');
    }
}

if ( $ENV{RPERL_VERBOSE} ) {
    Test::More::diag('[[[ Beginning Entirety Of Tests From The Inline::CPP POD ]]]');
}

# Inline::CPP examples from:  http://search.cpan.org/~davido/Inline-CPP-0.44/lib/Inline/CPP.pod
# Note: I did not exclude any Inline::CPP examples, they are all suitable for testing.

# class Farmer, AKA "Farmer Bob"
#RPerl::diag('in 03_inline_cpp.t, starting Farmer Bob...' . "\n");

my $farmer_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
class Farmer {
	public:
		Farmer(char *name, int age);
		~Farmer();

		int how_tired() { return tiredness; }
		int how_long() { return howlong; }
		void do_chores(int howlong);

	private:
		char *name;
		int age;
		int tiredness;
		int howlong;
};
Farmer::Farmer(char *name, int age) {
	this->name = strdup(name);
	this->age = age;
	tiredness = 0;
	howlong = 0;
}
Farmer::~Farmer() { free(name); }
void Farmer::do_chores(int hl) {
	howlong += hl;
	tiredness += (age * hl);
}
END_OF_CPP_CODE
EOF

#RPerl::diag('in 03_inline_cpp.t, building Farmer Bob...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $farmer_define_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, undef, q{Inline::CPP, define class Farmer returns correct value} );
    },
    q{Inline::CPP, define class Farmer lives}
);
my $farmer_call_eval_string = <<'EOF';
my $farmer = new Farmer("Ingy", 42);
my $slavedriver = 1;
while($farmer->how_tired < 420) {
	$farmer->do_chores($slavedriver);
	$slavedriver <<= 1;
}
return("Wow!  The farmer worked " . $farmer->how_long . " hours!");
EOF

#RPerl::diag('in 03_inline_cpp.t, running Farmer Bob...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $farmer_call_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, 'Wow!  The farmer worked 15 hours!', q{Inline::CPP, call Farmer methods returns correct value} );
    },
    q{Inline::CPP, call Farmer methods lives}
);

#RPerl::diag('in 03_inline_cpp.t, finished Farmer Bob!' . "\n");

# class Airplane, AKA "Plane and Simple"
#RPerl::diag('in 03_inline_cpp.t, starting Airplane...' . "\n");

my $airplane_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
using namespace std;
//#include <string.h>  // DEV NOTE: uncomment for possible solution to Github Issue #34    https://github.com/wbraswell/rperl/issues/34
#include <sstream>
/* Abstract class (interface) */
class Object {
	public:
		virtual void print() { cout << "Object (" << this << ")" << endl; }
		virtual char* nonprint() { ostringstream oretval;  oretval << "Object (" << this << ")";  return((char*)oretval.str().c_str()); }
//		virtual string nonprint() { ostringstream oretval; oretval << "Object (" << this << ")"; string ret = oretval.str(); return ret; }  // DEV NOTE: uncomment for possible solution to Github Issue #34
		virtual void info() = 0;
		virtual bool isa(char *klass) = 0;
		virtual bool can(char *method) = 0;
};
class Airplane : public Object {
	public:
		Airplane() {}
		~Airplane() {}
		virtual void info() { print(); }
		virtual bool isa(char *klass) { return strcmp(klass, "Object")==0; }
		virtual bool can(char *method) {
			bool yes = false;
			yes |= strcmp(method, "print")==0;
			yes |= strcmp(method, "info")==0;
			yes |= strcmp(method, "isa")==0;
			yes |= strcmp(method, "can")==0;
			return yes;
		}
};
END_OF_CPP_CODE
EOF

#RPerl::diag('in 03_inline_cpp.t, building Airplane...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $airplane_define_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, undef, q{Inline::CPP, define class Airplane returns correct value} );
    },
    q{Inline::CPP, define class Airplane lives}
);
my $airplane_call_eval_string = <<'EOF';
my $plane = new Airplane;
my $plane_retval1 = '';
#my $plane_retval1 = $plane->nonprint;  # DEV NOTE: uncomment when solution found for Github Issue #34    https://github.com/wbraswell/rperl/issues/34
my $plane_retval2 = '';
if ($plane->isa("Object")) { $plane_retval2 .= "Plane is an Object!"; }
unless ($plane->can("fly")) { $plane_retval2 .= "  This plane sucks!"; }
return($plane_retval1, $plane_retval2);
EOF

#RPerl::diag('in 03_inline_cpp.t, running Airplane...' . "\n");

lives_and(
    sub {
        my ( $airplane_retval1, $airplane_retval2 ) = eval $airplane_call_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
#        like( $airplane_retval1, '/Object\ \(0x\w*\)/', 'Inline::CPP, call Airplane methods, return correct value 1' );  # DEV NOTE: uncomment when solution found for Github Issue #34    https://github.com/wbraswell/rperl/issues/34
        is( $airplane_retval2, 'Plane is an Object!  This plane sucks!', 'Inline::CPP, call Airplane methods, return correct value 2' );
    },
    q{Inline::CPP, call Airplane methods lives}
);

#RPerl::diag('in 03_inline_cpp.t, finished Airplane!' . "\n");

# average() VS avg(), AKA "The Ellipsis Abridged"
#RPerl::diag('in 03_inline_cpp.t, starting Elipses Abridged...' . "\n");

my $average_define_eval_string = <<'EOF';
sub average {
	my $average = 0;
	for (my $i=0; $i<@_; $i++) {
		$average *= $i;
		$average += $_[$i];
		$average /= $i + 1;
	}
	return $average;
}
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
double avg(...) {
	Inline_Stack_Vars;
	double avg = 0.0;
	for (int i=0; i<items; i++) {
		avg *= i;
		avg += SvNV(ST(i));
		avg /= i + 1;
	}
	return avg;
}
END_OF_CPP_CODE
EOF

#RPerl::diag('in 03_inline_cpp.t, building Elipses Abridged...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $average_define_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, undef, q{Inline::CPP, define average() and avg() returns correct value} );
    },
    q{Inline::CPP, define average() and avg() lives}
);
my $average_call_eval_string = <<'EOF';
my @numbers = map { rand } (1 .. 10000);
my $average_retval = average(@numbers);
my $avg_retval = avg(@numbers);
my $average_retval_string = "The Perl average of 10000 random numbers is: " . $average_retval;
my $avg_retval_string = "The C/C++ average of 10000 random numbers is: " . $avg_retval;
return($average_retval, $avg_retval, $average_retval_string, $avg_retval_string);
EOF

#RPerl::diag('in 03_inline_cpp.t, running Elipses Abridged...' . "\n");

lives_and(
    sub {
        my ( $average_retval, $avg_retval, $average_retval_string, $avg_retval_string ) = eval $average_call_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        like(
            $average_retval_string,
            '/The Perl average of 10000 random numbers is: \d+.\d+/',
            q{Inline::CPP, call average() and avg() returns correct value 1}
        );
        like(
            $avg_retval_string,
            '/The C\/C\+\+ average of 10000 random numbers is: \d+.\d+/',
            q{Inline::CPP, call average() and avg() returns correct value 2}
        );
        delta_ok( $average_retval, $avg_retval, q{Inline::CPP, call average() and avg() return values are equivalent} );
    },
    q{Inline::CPP, call average() and avg() lives}
);

#RPerl::diag('in 03_inline_cpp.t, finished Elipses Abridged!' . "\n");

# classes Queue and Stack, AKA "Stacks and Queues"
#RPerl::diag('in 03_inline_cpp.t, starting Stacks & Queues...' . "\n");

my $queuestack_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
class Queue {
	public:
		Queue(int sz=0) { q = newAV(); if (sz) av_extend(q, sz-1); }
		~Queue() { av_undef(q); }
		int size() {return av_len(q) + 1; }
		int enqueue(SV* item) { av_push(q, SvREFCNT_inc(item)); return av_len(q)+1; }
		SV* dequeue() { return av_shift(q); }
		SV* peek() { return size() ? SvREFCNT_inc(*av_fetch(q,0,0)): &PL_sv_undef;}
	private:
		AV* q;
};
class Stack {
	public:
		Stack(int sz=0) { s = newAV(); if (sz) av_extend(s, sz-1); }
		~Stack() { av_undef(s); }
		int size() { return av_len(s) + 1; }
		int push(SV* i) { av_push(s, SvREFCNT_inc(i)); return av_len(s)+1; }
		SV* pop() { return av_pop(s); }
		SV* peek() { return size() ? SvREFCNT_inc(*av_fetch(s,size()-1,0)) : &PL_sv_undef; }
	private:
		AV* s;
};
END_OF_CPP_CODE
EOF

#RPerl::diag('in 03_inline_cpp.t, building Stacks & Queues...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $queuestack_define_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, undef, q{Inline::CPP, define classes Queue and Stack returns correct value} );
    },
    q{Inline::CPP, define classes Queue and Stack lives}
);
my $queuestack_call_eval_string = <<'EOF';
my $q = new Queue;
my $queue_retval = '';
$q->enqueue(50);
$q->enqueue("Where am I?");
$q->enqueue("In a queue.");
#RPerl::diag("There are " . $q->size . " items in the queue\n");
while($q->size) {
#	RPerl::diag("About to dequeue: " . $q->peek . "\n");
#	RPerl::diag("Actually dequeued: " . $q->dequeue . "\n");
	$queue_retval .= $q->dequeue . '  ';
}
my $s = new Stack;
my $stack_retval = '';
$s->push(42);
$s->push("What?");
#RPerl::diag("There are " . $s->size . " items on the stack\n");
while($s->size) {
#	RPerl::diag("About to pop: " . $s->peek . "\n");
#	RPerl::diag("Actually popped: " . $s->pop . "\n");
	$stack_retval .= $s->pop . '  ';
}
return($queue_retval, $stack_retval);
EOF

#RPerl::diag('in 03_inline_cpp.t, running Stacks & Queues...' . "\n");

lives_and(
    sub {
        my ( $queue_retval, $stack_retval ) = eval $queuestack_call_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $queue_retval, '50  Where am I?  In a queue.  ', q{Inline::CPP, call Queue and Stack methods returns correct value 1} );
        is( $stack_retval, 'What?  42  ', q{Inline::CPP, call Queue and Stack methods returns correct value 2} );
    },
    q{Inline::CPP, call Queue and Stack methods lives}
);

#RPerl::diag('in 03_inline_cpp.t, finished Stacks & Queues!' . "\n");

# multiadd(), AKA "Elipses Revisited (and Overloading or Templates)"
#RPerl::diag('in 03_inline_cpp.t, starting Elipses Revisited...' . "\n");

my $multiadd_define_eval_string = <<'EOF';
use Inline CPP => Config => CCFLAGSEX => '-DNO_XSLOCKS';
use Inline CPP => <<'END_OF_CPP_CODE';
#include <stdexcept>
// Inline::CPP won't create predictable bindings to overloaded functions.
int add ( int a, int b ) { return a + b; }
int add ( int a, int b, int c ) { return a + b + c; }
// But a function call with elipses can dispatch to overloaded functions since
// no Perl binding is required in reaching those functions.
int multiadd ( SV*  a, ... ) {
	dXSARGS;  // Creates a variable 'items' that contains a paramater count.
	SV* saved_error_message = NULL;  // NEW LINE
	try{
		switch ( items ) {
			case 1:  return SvIV(ST(0));
			case 2:  return add( SvIV(ST(0)), SvIV(ST(1)) );
			case 3:  return add( SvIV(ST(0)), SvIV(ST(1)), SvIV(ST(2)) );
			default: throw std::runtime_error( "multiadd() - Too many args in function call" );
		}
	}
//	catch ( std::runtime_error msg ) { croak( msg.what() );  }  // Perl likes croak for exceptions.  THIS DOES NOT WORK IN WINDOWS!
	catch ( std::runtime_error msg ) { saved_error_message = sv_2mortal(newSVpv(msg.what(), 0));  }  // Perl likes croak for exceptions.  NEW LINE
//	if (saved_error_message) { croak_sv( saved_error_message ); }  // this does not work in Perl < v5.13.1  NEW LINE
	if (saved_error_message) { croak( SvPV_nolen(saved_error_message) ); }  // NEW LINE
}
END_OF_CPP_CODE
EOF

#RPerl::diag('in 03_inline_cpp.t, building Elipses Revisited...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval $multiadd_define_eval_string;
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        is( $EVAL_RETVAL, undef, q{Inline::CPP, define multiadd() returns correct value} );
    },
    q{Inline::CPP, define multiadd() lives}
);

#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 1/4...' . "\n");

lives_and(
    sub {
        my $EVAL_RETVAL = eval 'return(multiadd(1));  # No dispatch; just return the value';
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        cmp_ok( $EVAL_RETVAL, q{==}, 1, q{Inline::CPP, call multiadd(1) returns correct value} );
    },
    q{Inline::CPP, call multiadd(1) lives}
);

#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 2/4...' . "\n");
lives_and(
    sub {
        my $EVAL_RETVAL = eval 'multiadd(1, 2);  # Dispatch add(int, int)';
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        cmp_ok( $EVAL_RETVAL, q{==}, 3, q{Inline::CPP, call multiadd(1, 2) returns correct value} );
    },
    q{Inline::CPP, call multiadd(1, 2) lives}
);

#RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 3/4...' . "\n");
lives_and(
    sub {
        my $EVAL_RETVAL = eval 'multiadd(1, 2, 3);  # Dispatch add(int, int, int)';
        if ( $EVAL_ERROR ne q{} ) {
            croak("Error in eval, have \$EVAL_ERROR =\n\nBEGIN EVAL ERROR\n\n$EVAL_ERROR\n\nEND EVAL ERROR\n\ncroaking");
        }
        cmp_ok( $EVAL_RETVAL, q{==}, 6, q{Inline::CPP, call multiadd(1, 2, 3) returns correct value} );
    },
    q{Inline::CPP, call multiadd(1, 2, 3) lives}
);

# NEED FIX: get Elipses Revisited part 4/4 working in Windows
if ( $OSNAME eq 'MSWin32' ) {
#    RPerl::diag('in 03_inline_cpp.t, skipping Elipses Revisited part 4/4, Windows detected...' . "\n");
    if ( $ENV{RPERL_VERBOSE} ) {
        Test::More::diag("[[[ MS Windows OS Detected, Inline::CPP Exception Temporarily Disabled, Skipping Elipses Revisited Part 4/4 Test, RPerl Inline System ]]]");
    }
    ok(1, q{Inline::CPP, call multiadd(1, 2, 3, 4) skipped on MS Windows OS});
}
else {
#    RPerl::diag('in 03_inline_cpp.t, running Elipses Revisited part 4/4...' . "\n");
    lives_and(    # can't use throws_ok() because we are trapping the exception inside of eval
        sub {
            my $EVAL_RETVAL = eval 'multiadd(1, 2, 3, 4);  # No dispatch; throw an exception';
            like( $EVAL_ERROR, '/^multiadd\(\) \- Too many args in function call at/', q{Inline::CPP, call multiadd(1, 2, 3, 4) throws correct exception} );
        },
        q{Inline::CPP, call multiadd(1, 2, 3, 4) lives}
    );
}

#RPerl::diag('in 03_inline_cpp.t, finished Elipses Revisited!' . "\n");

done_testing();
