use strict;
use warnings;

use Test::More tests => 41;

use Test::Number::Delta;
use Try::Tiny;

# load module

BEGIN { use_ok("DoseMe::Algorithms::Distributions::BCCG", qw/ bccg_cdf /); }


### Basic usage of bccg_cdf() function

my $precision = 1e-12;

# Dies with no args (needs "q" at least)
try {
    my $p = bccg_cdf();
    fail("Did not die as expected without input \"q\"");
} catch {
    pass("### -- BCCG_CDF.Params.UndefinedQ | Dies as expected without input \"q\"");
};

my @q_vals = (undef, "foo", '', [], {}, sub {1} );
# Dies when 'q' is a value that would not pass:  looks_like_number($q)
foreach my $q (@q_vals) {
    try {
        my $p = bccg_cdf($q);
        fail('Did not die as expected when q == "' . (defined($q) ? $q : 'undef') . '"');
    } catch {
        pass("### -- BCCG_CDF.Params.InvalidQ | Dies as expected when q == \"" . (defined($q) ? $q : 'undef') . "\"");
    };
}

my @mu_vals = (0, "foo", '', -$precision, [], {}, sub {1} );
# Dies when 'mu' is a value that would not pass:  looks_like_number($mu) && $mu > 0
foreach my $mu (@mu_vals) {
    try {
        my $p = bccg_cdf(1, $mu);
        fail('Did not die as expected when nu == "' . (defined($mu) ? $mu : 'undef') . '"');
    } catch {
        pass("### -- BCCG_CDF.Params.InvalidMu | Dies as expected when nu == \"" . (defined($mu) ? $mu : 'undef') . "\"");
    };
}
try {
    ok(bccg_cdf(1, $precision), "### -- BCCG_CDF.Params.Valid.PositiveNonZeroMu | Allows mu > 0");
} catch {
    fail('Died unexpectedly when valid mu == ' . $precision);
};

my @sigma_vals = (0, "foo", '', -$precision, [], {}, sub {1} );
# Dies when 'sigma' is a value that would not pass:  looks_like_number($sigma) && $sigma > 0
foreach my $sigma (@sigma_vals) {
    try {
        my $p = bccg_cdf(1, undef, $sigma);
        fail('Did not die as expected when sigma == "' . (defined($sigma) ? $sigma : 'undef') . '"');
    } catch {
        pass("### -- BCCG_CDF.Params.InvalidSigma | Dies as expected when sigma == \"" . (defined($sigma) ? $sigma : 'undef') . "\"");
    };
}
try {
    ok(bccg_cdf(1, 1, $precision), "### -- BCCG_CDF.Params.Valid.PositiveNonZeroSigma | Allows sigma > 0");
} catch {
    fail('Died unexpectedly when valid sigma == ' . $precision);
};

my @nu_vals = ("foo", '', [], {}, sub {1} );
# Dies when 'nu' is a value that would not pass:  looks_like_number($nu)
foreach my $nu (@nu_vals) {
    try {
        my $p = bccg_cdf(1, undef, undef, $nu);
        fail('Did not die as expected when nu == "' . (defined($nu) ? $nu : 'undef') . '"');
    } catch {
        pass("### -- BCCG_CDF.Params.InvalidNu | Dies as expected when nu == \"" . (defined($nu) ? $nu : 'undef') . "\"");
    };
}

# Test lower_tail param usage - 1 and 0
try {
    my $p_lower = bccg_cdf(1, undef, undef, undef, 1);
    pass("### -- BCCG_CDF.Params.Valid.LowerTail.True | Allows lower_tail == 1 input");
} catch {
    fail('Failed to allow lower_tail == 1 param input');
};
try {
    my $p_upper = bccg_cdf(1, undef, undef, undef, 0);
    pass("### -- BCCG_CDF.Params.Valid.LowerTail.False | Allows lower_tail == 0 input");
} catch {
    fail('Failed to allow lower_tail == 0 param input');
};

# Test log_p param usage - 1 and 0
try {
    my $p_logged = bccg_cdf(1, undef, undef, undef, 1, 1);
    pass("### -- BCCG_CDF.Params.Valid.LogP.True | Allows log_p == 1 input");
} catch {
    fail('Failed to allow log_p == 1 param input');
};
try {
    my $p = bccg_cdf(1, undef, undef, undef, 1, 0);
    pass("### -- BCCG_CDF.Params.Valid.LogP.False | Allows log_p == 0 input");
} catch {
    fail('Failed to allow log_p == 0 param input');
};


### Basic numerical unit tests for bccg_cdf()

# Note: Have tried tighter than 1.5e-6 but cannot achieve better
my $epsilon = 1.5e-6;

# Note: Numerical tests with expected values from R gamlss.dist pBCCG()
my $expected = 0;
delta_within(bccg_cdf(0), $expected, $epsilon, 'Expected output when q == 0, all params default');

$expected = 0.5;
delta_within(bccg_cdf(1), $expected, $epsilon, 'Expected output when q == 1, all params default');

$expected = 0.5000399;
delta_within(bccg_cdf(1.00001), $expected, $epsilon, 'Expected output when q == 1.00001, all params default');

$expected = 0.4999601; # i.e. 1 - 0.5000399;
my @default_mu_sigma_nu = (undef, undef, undef);
my $lower_tail_false = 0;
delta_within(bccg_cdf(1.00001, @default_mu_sigma_nu, $lower_tail_false), $expected, $epsilon,
    'Expected output when q == 1.00001 and lower_tail=false');

$expected = -0.693227;
my $log_p_true = 1;
delta_within(bccg_cdf(1.00001, @default_mu_sigma_nu, $lower_tail_false, $log_p_true), $expected, $epsilon,
    'Expected output when q == 1.00001 and lower_tail=false and log_p=true');

$expected = -7.619853e-24;
delta_within(bccg_cdf(-1), $expected, $epsilon, 'Expected output when q == -1, all params default');

try {
    # Some test cases based on BCCG parameters for Goti et al Adult Vancomycin 2-compartment model
    my $age_cdf = bccg_cdf(50, 57, 1.18667977217638, 0.356644080166454);
    my $weight_cdf = bccg_cdf(75, 79, 1.48013622930835, -0.269242369185731);
    my $age_cdf_expected = 0.4520785;
    my $weight_cdf_expected = 0.4888565;
    delta_within($age_cdf, $age_cdf_expected, $epsilon, 'Expected output for age CDF test');
    delta_within($weight_cdf, $weight_cdf_expected, $epsilon, 'Expected output for weight CDF test');
} catch {
    fail('Unexpected failure in bccg_cdf() for age or weight: ' . $_);
};

