use strict;
use warnings;

use Test::More tests => 97;
use Test::Number::Delta;
use Try::Tiny;
use List::Util qw/ any /;


use DoseMe::Drugs::Exampillin;
use DoseMe::Drugs::Vancomycin::Neonate;
use DoseMe::Drugs::Vancomycin;

# load module
BEGIN { use_ok("DoseMe::Algorithms::PopulationModelSuitability", qw/ estimate_population_model_suitability_score estimate_population_model_suitability_significance /); }

# verify correct failure when patient is undefined
my $patient_pop_distributions = {
    age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
    weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
    background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
    female     => { p => 0.4652318 } # Binomial probability for females in model population
};

try {
    my $test_score = estimate_population_model_suitability_score(undef, $patient_pop_distributions);
    fail('Did not die as expected when patient is undefined');
} catch {
    pass("### -- PatientParams.Undefined.True | Dies as expected when patient is undefined");
};

# verify correct failure when the height of a patient is invalid (i.e. not nonnegative)
my @height_vals = ("foo", '', -1, [], {}, sub {1} );
foreach my $height (@height_vals) {
    my %patient = (
        height => $height,
        age => 61.8158389104368,
        weight => 64,
        sex => 'Male'
    );

    try {
        my $test_score = estimate_population_model_suitability_score(\%patient, $patient_pop_distributions);
        fail('Did not die as expected when patient height == "' . (defined($patient{height}) ? $patient{height} : 'undef') . '"');
    } catch {
        pass("### -- PatientParams.InvalidHeight | Dies as expected when height == \"" . (defined($patient{height}) ? $patient{height} : 'undef') . "\"");
    };
}

# verify correct failure when the age of a patient is invalid (i.e. not nonnegative)
my @age_vals = ("foo", '', -1, [], {}, sub {1} );
foreach my $age (@age_vals) {
    my %patient = (
        height => 176,
        age => $age,
        weight => 64,
        sex => 'Male'
    );

    try {
        my $test_score = estimate_population_model_suitability_score(\%patient, $patient_pop_distributions);
        fail('Did not die as expected when patient age == "' . (defined($patient{age}) ? $patient{age} : 'undef') . '"');
    } catch {
        pass("### -- PatientParams.InvalidAge | Dies as expected when age == \"" . (defined($patient{age}) ? $patient{age} : 'undef') . "\"");
    };
}

# verify correct failure when the weight of a patient is invalid (i.e. not nonnegative)
my @weight_vals = ("foo", '', -1, [], {}, sub {1} );
foreach my $weight (@weight_vals) {
    my %patient = (
        height => 176,
        age => 61.8158389104368,
        weight => $weight,
        sex => 'Male'
    );

    try {
        my $test_score = estimate_population_model_suitability_score(\%patient, $patient_pop_distributions);
        fail('Did not die as expected when patient weight == "' . (defined($patient{weight}) ? $patient{weight} : 'undef') . '"');
    } catch {
        pass("### -- PatientParams.InvalidWeight | Dies as expected when weight == \"" . (defined($patient{weight}) ? $patient{weight} : 'undef') . "\"");
    };
}

# verify correct failure when the sex of a patient is invalid
my @sex_vals = (0, 1, "foo", '', -1, [], {}, sub {1} );
foreach my $sex (@sex_vals) {
    my %patient = (
        height => 176,
        age => 61.8158389104368,
        weight => 64,
        sex => $sex
    );

    try {
        my $test_score = estimate_population_model_suitability_score(\%patient, $patient_pop_distributions);
        fail('Did not die as expected when patient sex == "' . (defined($patient{sex}) ? $patient{sex} : 'undef') . '"');
    } catch {
        pass("### -- PatientParams.InvalidSex | Dies as expected when sex == \"" . (defined($patient{sex}) ? $patient{sex} : 'undef') . "\"");
    };
}

# verify correct failure for the different BCCG params of age, weight and background, and binom p of females
my %patient = (
    height => 176,
    age => 61.8158389104368,
    weight => 64,
    sex => 'Male'
    );

my $exampillin = DoseMe::Drugs::Exampillin->new(patient => \%patient);

# verify correct failure when the mu parameter for Age BCCG distribution of patient population is invalid
my @mu_vals = (0, "foo", '', -1, [], {}, sub {1} );
foreach my $mu (@mu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => $mu, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when mu == "' . (defined($patient_pop_distributions->{age}{mu}) ? $patient_pop_distributions->{age}{mu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Age.InvalidMu | Dies as expected when mu == \"" . (defined($patient_pop_distributions->{age}{mu}) ? $patient_pop_distributions->{age}{mu} : 'undef') . "\"");
    };
}

# verify correct failure when the sigma parameter for Age BCCG distribution of patient population is invalid
my @sigma_vals = (0, "foo", '', -1, [], {}, sub {1} );
foreach my $sigma (@sigma_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => $sigma, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when sigma == "' . (defined($patient_pop_distributions->{age}{sigma}) ? $patient_pop_distributions->{age}{sigma} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Age.InvalidSigma | Dies as expected when sigma == \"" . (defined($patient_pop_distributions->{age}{sigma}) ? $patient_pop_distributions->{age}{sigma} : 'undef') . "\"");
    };
}

# verify correct failure when the nu parameter for Age BCCG distribution of patient population is invalid
my @nu_vals = ("foo", '', [], {}, sub {1} );
foreach my $nu (@nu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => $nu }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when nu == "' . (defined($patient_pop_distributions->{age}{nu}) ? $patient_pop_distributions->{age}{nu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Age.InvalidNu | Dies as expected when nu == \"" . (defined($patient_pop_distributions->{age}{nu}) ? $patient_pop_distributions->{age}{nu} : 'undef') . "\"");
    };
}

# verify correct failure when the mu parameter for Weight BCCG distribution of patient population is invalid
foreach my $mu (@mu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => $mu, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when mu == "' . (defined($patient_pop_distributions->{weight}{mu}) ? $patient_pop_distributions->{weight}{mu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Weight.InvalidMu | Dies as expected when mu == \"" . (defined($patient_pop_distributions->{weight}{mu}) ? $patient_pop_distributions->{weight}{mu} : 'undef') . "\"");
    };
}

# verify correct failure when the sigma parameter for Weight BCCG distribution of patient population is invalid
foreach my $sigma (@sigma_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => $sigma, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when sigma == "' . (defined($patient_pop_distributions->{weight}{sigma}) ? $patient_pop_distributions->{weight}{sigma} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Weight.InvalidSigma | Dies as expected when sigma == \"" . (defined($patient_pop_distributions->{weight}{sigma}) ? $patient_pop_distributions->{weight}{sigma} : 'undef') . "\"");
    };
}

# verify correct failure when the nu parameter for Weight BCCG distribution of patient population is invalid
foreach my $nu (@nu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => $nu }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when nu == "' . (defined($patient_pop_distributions->{weight}{nu}) ? $patient_pop_distributions->{weight}{nu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Weight.InvalidNu | Dies as expected when nu == \"" . (defined($patient_pop_distributions->{weight}{nu}) ? $patient_pop_distributions->{weight}{nu} : 'undef') . "\"");
    };
}

# verify correct failure when the binomial p value for female in patient population is invalid
my @p_vals = ( 52, "foo", '', -1, [], {}, sub {1} );
foreach my $p (@p_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => $p } # Binomial probability for females in model population
    };

    try {
        my $test_score = estimate_population_model_suitability_score($exampillin->{patient}, $patient_pop_distributions);
        fail('Did not die as expected when female P == "' . (defined($patient_pop_distributions->{female}{p}) ? $patient_pop_distributions->{female}{p} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Female.InvalidP | Dies as expected when female P == \"" . (defined($patient_pop_distributions->{female}{p}) ? $patient_pop_distributions->{female}{p} : 'undef') . "\"");
    };
}

# verify correct failure when the mu parameter for Background BCCG distribution of patient population is invalid
foreach my $mu (@mu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => $mu, sigma => 1.838612957, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score_sig = estimate_population_model_suitability_significance(0.113309646886279, $patient_pop_distributions);
        fail('Did not die as expected when mu == "' . (defined($patient_pop_distributions->{background}{mu}) ? $patient_pop_distributions->{background}{mu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Background.InvalidMu | Dies as expected when mu == \"" . (defined($patient_pop_distributions->{background}{mu}) ? $patient_pop_distributions->{background}{mu} : 'undef') . "\"");
    };
}

# verify correct failure when the sigma parameter for Background BCCG distribution of patient population is invalid
foreach my $sigma (@sigma_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => $sigma, nu => 0.221412120 }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score_sig = estimate_population_model_suitability_significance(0.113309646886279, $patient_pop_distributions);
        fail('Did not die as expected when sigma == "' . (defined($patient_pop_distributions->{background}{sigma}) ? $patient_pop_distributions->{background}{sigma} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Background.InvalidSigma | Dies as expected when sigma == \"" . (defined($patient_pop_distributions->{background}{sigma}) ? $patient_pop_distributions->{background}{sigma} : 'undef') . "\"");
    };
}

# verify correct failure when the nu parameter for Background BCCG distribution of patient population is invalid
foreach my $nu (@nu_vals) {
    my $patient_pop_distributions = {
        age        => { mu => 57, sigma => 1.18667977217638, nu => 0.356644080166454 }, # Mu, Sigma, and Nu for age BCCG distribution
        weight     => { mu => 79, sigma => 1.48013622930835, nu => -0.269242369185731 }, # Mu, Sigma, and Nu for weight BCCG distribution
        background => { mu => 0.007270899, sigma => 1.838612957, nu => $nu }, # Mu, Sigma, and Nu of BCCG distribution of background (pop suitability scores)
        female     => { p => 0.4652318 } # Binomial probability for females in model population
    };

    try {
        my $test_score_sig = estimate_population_model_suitability_significance(0.113309646886279, $patient_pop_distributions);
        fail('Did not die as expected when nu == "' . (defined($patient_pop_distributions->{background}{nu}) ? $patient_pop_distributions->{background}{nu} : 'undef') . '"');
    } catch {
        pass("### -- PatientPopDistributions.Background.InvalidNu | Dies as expected when nu == \"" . (defined($patient_pop_distributions->{background}{nu}) ? $patient_pop_distributions->{background}{nu} : 'undef') . "\"");
    };
}

my $vancomycin = DoseMe::Drugs::Vancomycin->new(patient => \%patient); # For background patient pop distribution
my $mod_score = estimate_population_model_suitability_score($exampillin->{patient}, $exampillin->patient_pop_distributions);
my $mod_score_sig = estimate_population_model_suitability_significance($mod_score, $vancomycin->patient_pop_distributions);

# Estimate score and significance default test
my $epsilon = 1e-9;

print "Population model suitability score for exampillin: $mod_score\n";
delta_within(estimate_population_model_suitability_score($exampillin->{patient}, $exampillin->patient_pop_distributions), 0.113309646886279, $epsilon, 'Expected suitability score for exampillin');


print "Significance of population model suitability score for exampillin with vancomycin background: $mod_score_sig\n";
delta_within(estimate_population_model_suitability_significance($mod_score, $vancomycin->patient_pop_distributions), 0.020990447245918, $epsilon, 'Expected suitability score significance for exampillin with vancomycin background');


# Test for undefined patient population distribution
my %patient_neo = (
    height => 100,
    age => 5*7/365.25,
    weight => 2.9,
    sex => 'Male'
);

my $neonate = DoseMe::Drugs::Vancomycin::Neonate->new(patient => \%patient_neo);
my $vancomycin_neo = DoseMe::Drugs::Vancomycin->new(patient => \%patient_neo);

try {
    my $mod_score_neo = estimate_population_model_suitability_score($neonate->{patient}, $neonate->patient_pop_distributions);
    pass("### -- PatientPopDistributions.Undefined.True | Allows undefined patient population distributions");
} catch {
    fail("Failed to allow undefined patient population distributions");
};

# Test for undefined population model suitability score or undefined background population distribution

try {
    my $mod_score_neo = undef;
    my $mod_score_sig_neo = estimate_population_model_suitability_significance($mod_score_neo, $vancomycin_neo->patient_pop_distributions);
    pass("### -- PopModSuitabilityScore.Undefined.True | Allows undefined population model suitability score");
} catch {
    fail("Failed to allow undefined population model suitability score");
};

# Test for undefined background population distribution
try {
    my $mod_score_sig_neo = estimate_population_model_suitability_significance($mod_score, $neonate->patient_pop_distributions);
    pass("### -- BackgroundPopDistributions.Undefined.True | Allows undefined background population distributions");
} catch {
    fail("Failed to allow undefined background population distributions");
};

