#include <stdexcept>

#include <aeryn/test_runner.hpp>
#include <aeryn/is_equal.hpp>
#include <aeryn/use_name.hpp>
#include <aeryn/throws_exception.hpp>

#include "factorial_gmp.hpp"

const std::string data[][2] = {
  { "0" , "1" } ,
  { "1" , "1" } , 
  { "2" , "2" } ,
  { "3" , "6" } ,
  { "4" , "24" } ,
  { "5" , "120" } ,
  { "6" , "720" } ,
  { "7" , "5040" } ,
  { "8" , "40320" } ,
  { "9" , "362880" } ,
  { "10" , "3628800" } ,
  { "11" , "39916800" } ,
  { "12" , "479001600" } ,
  { "13" , "6227020800" } ,
  { "14" , "87178291200" } ,
  { "20" , "2432902008176640000" } ,
  { "30" , "265252859812191058636308480000000" } ,
  { "40" , "815915283247897734345611269596115894272000000000" }
} ;

//////  There must be a way of calculating this number rather than having to know it a priori.

const int dataCount = 18 ;

#define correctTest( method ) for ( int i = 0 ; i < dataCount ; ++i ) { IS_EQUAL ( mpz_class ( data[i][1] ) , Factorial::method ( mpz_class ( data[i][0] ) ) ) ; }

void correctIterative ( ) { correctTest ( iterative ) ; }
void correctRecursive ( ) { correctTest ( recursive ) ; }
void correctTailRecursive ( ) { correctTest ( tailRecursive ) ; }

#define negativeTest( method )   for ( int i = -20 ; i < 0 ; ++i ) { THROWS_EXCEPTION ( Factorial::method ( i )  , std::invalid_argument ) ; }

void negativeIterative ( ) { negativeTest ( iterative ) ; }
void negativeRecursive ( ) { negativeTest ( recursive ) ; }
void negativeTailRecursive ( ) { negativeTest ( tailRecursive ) ; }

void iterativeEnormousSucceeds ( ) { Factorial::iterative ( 10000 ) ; }
void recursiveStackFailsToFail ( ) { Factorial::recursive ( 10000 ) ; }
void tailRecursiveStackFailsToFail ( ) { Factorial::tailRecursive ( 10000 ) ; }

int main ( ) {
  using namespace Aeryn ;
  TestRunner testRunner ;  
  testRunner.Add ( TestCase ( USE_NAME ( correctIterative ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( correctRecursive ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( correctTailRecursive ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( negativeIterative ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( negativeRecursive ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( negativeTailRecursive ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( iterativeEnormousSucceeds ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( recursiveStackFailsToFail ) ) ) ;
  testRunner.Add ( TestCase ( USE_NAME ( tailRecursiveStackFailsToFail ) ) ) ;
  return testRunner.Run ( ) ;  
}
