use strict;
use warnings;

use Test::More tests => 244;
use Test::Number::Delta;
use Test::Exception;
use Math::Trig;
use List::Util qw(min max);
use Scalar::Util qw(looks_like_number);

use DoseMe::Drugs::Exampillin;
use DoseMe::Drugs::Amikacin;

# load module

BEGIN { use_ok("DoseMe::Algorithms::PlasmaConcentrationPlot", qw/ adaptive_concentration_plot goldensectionpeak trim_graph PHI HALF_PI /); }

# enable parameters in return value
$DoseMe::Algorithms::PlasmaConcentrationPlot::debug_level = 1;

# test constants

delta_within( PHI, 1.61803398874989, 1e-12, "Phi pass");
delta_within( HALF_PI, 1.57079632679490, 1e-12, "Pi/2 pass");

# basic golden section peak search tests

sub gspeaktest {
  my ($case, $plot_fn, $min, $max, $expected_value, $params) = @_;

  my $retval = goldensectionpeak($plot_fn, $min, $max, $params);

  my $precision = $retval->{precision};
  my $type = $retval->{type};

  my $peakval = $retval->{peak}->{y};

  delta_within($peakval, $expected_value,  $precision, "goldensectionpeak $case: peak value $peakval correct, precision = $precision on $type");

  # check that function preserved

  my @graph;
  push @graph, @{$retval->{leading}};
  push @graph, $retval->{peak};
  push @graph, @{$retval->{trailing}};

  my @expected_ys = map { $plot_fn->($_->{x}) } @graph;
  my @returned_ys = map { $_->{y} } @graph;
  delta_within(\@returned_ys, \@expected_ys, 1e-9, "goldensectionpeak $case: generating function preserved");

  # test precision
  my $left;
  my $right;
  my $achieved_precision;

  if ((scalar @{$retval->{leading}} > 0) and (scalar @{$retval->{trailing}} >0)) {
    if($type eq 'range') {
      $right = $retval->{trailing}->[0]->{y};
      $left  = $retval->{leading}->[-1]->{y};
      $achieved_precision = min (abs($right - $peakval), abs($peakval - $left));
    } else { # domain
      $right = $retval->{trailing}->[0]->{x};
      $left  = $retval->{leading}->[-1]->{x};
      $achieved_precision = min (abs($right - $retval->{peak}->{x}), abs($retval->{peak}->{x} - $left));
    }
    delta_within( $achieved_precision, 0, $precision, "goldensectionpeak $case: achieved precision $achieved_precision with specified precision = $precision on $type");
  }

  note("goldensectionpeak $case:tests finished.");
}

# concentration plot defaults tests

sub plotparamstest {
  my ($case, $params, $param_ev_pairs) = @_;

  my %patient = (
      'height' => 176,
      'age' => '61.8158389104368',
      'weight' => '64',
      'sex' => 'M'
      );

  my @doses = ();
  my $ndoses = 4;
  for (my $i=0; $i < $ndoses; $i++) {
    push @doses, ({
      'rate' => 1000,
      'length' => 0, # oral
      'time' => 24*$i
      });
  }

  ok(my $dm = DoseMe::Drugs::Exampillin->new(patient => \%patient), "params $case: can create model");
  $dm->doses(\@doses);

  my $retval = adaptive_concentration_plot($dm, $params);

  my @params          = map { $_->[0] } @{$param_ev_pairs};
  my @expected_values = map { $_->[1] } @{$param_ev_pairs};
  my @values          = map { $retval->{p}->{$_} } @params;

  note("params $case checking: " . join(', ', @params));

  # it is possible to compare arrays, but in this case output when there is a mismatch is not very useful
  # so we explicitly iterate over arrays
  for(my $index = 0; $index < (scalar @params); $index++) {
    if( defined($expected_values[$index]) and looks_like_number($expected_values[$index]) ) {
      delta_within($values[$index], $expected_values[$index], 1e-9, "params $case: $params[$index] = $expected_values[$index]");
    } elsif( defined($expected_values[$index]) ) {
      cmp_ok( $values[$index], 'eq', $expected_values[$index], "params $case: $params[$index] eq $expected_values[$index]" );
    } else { # assume it's a string
      is($values[$index], undef, "params $case: $params[$index] is undefined");
    }
  }
}

# basic concentration plot tests

sub graphtest {
  my ($case, $plot_fn, $end_of_adm_t, $exp_peak_t, $exp_peak_val) = @_;

  my $dose_times = [0, 2*pi, 4*pi];
  my $end_of_administration_times = $end_of_adm_t;

  my $retval = DoseMe::Algorithms::PlasmaConcentrationPlot::_adaptive_concentration_plot(
                    $plot_fn,
                    $dose_times,
                    $end_of_administration_times,
                    {t_start=>0, t_end=>6*pi, seekpeak=>1} );

  my $graph = $retval->{graph};

  # check that generating function is preserved

  my @expected_ys = map { $plot_fn->($_->{x}) } @{$graph};
  my @returned_ys = map { $_->{y} } @{$graph};

  delta_within(\@returned_ys, \@expected_ys, 1e-9, "$case: generating function preserved");

  # check that graph x values are monotonic

  my @xs = map { $_->{x} } @{$graph};
  my @sorted_xs = sort { $a <=> $b } @xs;
  cmp_ok( join('',@xs), 'eq', join('',@sorted_xs), "$case: graph time is monotonic" );

  # check there are no duplicates

  my @gaps = map { $xs[$_] - $xs[$_-1] } 1 .. $#xs;
  my @duplicates = grep { abs($_) < 1e-12 } @gaps;
  cmp_ok( (scalar @duplicates), '==', 0, "$case: no duplicates in graph" );

  # check troughs

  my @expected_trough_times = @{$dose_times};
  push @expected_trough_times, 6*pi; # end of graph is also a trough

  my @expected_trough_matrix = map { [$_, $plot_fn->($_)] } @expected_trough_times;
  my @returned_trough_matrix = map { [$_->{t}, $_->{c}] } @{$retval->{troughs}};

  delta_within(\@returned_trough_matrix, \@expected_trough_matrix, 1e-9, "$case: troughs correct");

  # check peaks

  my @expected_peak_times    = @{$exp_peak_t};
  my @expected_peak_values   = @{$exp_peak_val};
  my @returned_peak_times    = map { $_->{t} } @{$retval->{peaks}};
  my @returned_peak_values   = map { $_->{c} } @{$retval->{peaks}};

  # shortest time resolution is 1 minute by default, so expect these to be within 1 minute
  delta_within(\@returned_peak_times , \@expected_peak_times,  1/60, "$case: peak times correct");
  # these are discovered using numerical methods, so lower precision is expected
  delta_within(\@returned_peak_values, \@expected_peak_values, 1e-4, "$case: peak values correct");

  my $npoints = scalar @{$graph};
  note("$case: tests finished, generated $npoints points.");
  return ($npoints); # return number of points in graph so we can compare
}

my $init_exampilin = sub  {
  my ($case, $tau) = @_;

  my %patient = (
      'height' => 176,
      'age' => '61.8158389104368',
      'weight' => '64',
      'sex' => 'M'
      );

  my @doses = ();
  my $ndoses = 4;
  for (my $i=0; $i < $ndoses; $i++) {
  push @doses, ({
    'rate' => 1000,
    'length' => 0, # oral
    'time' => $tau*$i
    });
  }

  ok(my $dm = DoseMe::Drugs::Exampillin->new(patient => \%patient), "$case: can create model");
  $dm->doses(\@doses);

  return $dm;
};

my $init_amikacin = sub {
  my ($case, $tau) = @_;

  my @doses = (
    {
    'rate' => '141.666666666667',
    'length' => '0.6',
    'time' => 0
    },
    {
    'time' => 12,
    'length' => '0.5',
    'rate' => 200
    },
    {
    'time' => 24,
    'length' => '0.6',
    'rate' => '141.666666666667'
    },
    {
    'rate' => 200,
    'length' => '0.5',
    'time' => 36
    },
    {
    'time' => 48,
    'rate' => '141.666666666667',
    'length' => '0.6'
    },
    {
    'time' => 60,
    'length' => '0.5',
    'rate' => 200
    },
    {
    'length' => '0.6',
    'rate' => '141.666666666667',
    'time' => 72
    },
    {
    'length' => '0.5',
    'rate' => 200,
    'time' => 84
    },
    {
    'time' => 96,
    'rate' => '141.666666666667',
    'length' => '0.6'
    },
    {
    'rate' => 200,
    'length' => '0.5',
    'time' => 108
    },
    {
    'time' => 120,
    'length' => '0.6',
    'rate' => '141.666666666667'
    },
    {
    'rate' => 200,
    'length' => '0.5',
    'time' => 132
    },
    {
    'time' => '212.644166666667',
    'length' => '1e-12',
    'rate' => 0
    }
  );
  my @responses = (
     {
      'conc' => '4.2',
      'time' => '72.5333333333333',
      'secr' => '23'
     }
  );
  my %patient = (
    'height' => 176,
    'age' => '61.8158389104368',
    'weight' => '54',
    'sex' => 'M'
  );

  ok(my $dm = DoseMe::Drugs::Amikacin->new(patient => \%patient), "$case: can create model");
  $dm->doses(\@doses);
  $dm->responses(\@responses);

  return $dm;
};

sub drugtest {
  my ($dm_init_fn, $case, $tau, $exp_peak_t, $exp_trough_t, $params) = @_;

  $params //= {};

  my $dm = $dm_init_fn->($case, $tau);

  my $retval = adaptive_concentration_plot($dm, $params);

  my $graph = $retval->{'graph'};
  note("$case: " . scalar @{$graph} . " points generated");

  # check if generating function preserved
  my @expected_ys = map { $dm->_get_serum_level($_->{x}) } @{$graph};
  my @returned_ys = map { $_->{y} } @{$graph};

  delta_within(\@returned_ys, \@expected_ys, 1e-9, "$case: generating function preserved");

  # check that graph x values are monotonic

  my @xs = map { $_->{x} } @{$graph};
  my @sorted_xs = sort { $a <=> $b } @xs;
  cmp_ok( join('',@xs), 'eq', join('',@sorted_xs), "$case: graph time is monotonic" );

  # check there are no duplicates

  my @gaps = map { $xs[$_] - $xs[$_-1] } 1 .. $#xs;
  my @duplicates = grep { abs($_) < 1e-12 } @gaps;
  # FIXME: algorithm generates duplicate values on exampillin after exampillin conversion to use observations
  cmp_ok( (scalar @duplicates), '==', 0, "$case: no duplicates in graph" ) unless $params->{test_skip_no_duplicates};

  # output peak and trough tables
  note("\tpeaks");
  note("\txp\typ");
  for my $row (@{$retval->{peaks}}) {
    note("\t" . $row->{t} . "\t" . $row->{c} . "\n")
  }

  note("\ttroughs");
  note("\txt\tyt");
  for my $row (@{$retval->{troughs}}) {
    note("\t" . $row->{t} . "\t" . $row->{c} . "\n");
  }

  # compare peaks and troughs with expected values
  my @returned_trough_times  = map { $_->{t} } @{$retval->{troughs}};
  my @returned_peak_times    = map { $_->{t} } @{$retval->{peaks}};

  delta_within(\@returned_trough_times, $exp_trough_t, 1e-9, "$case: trough times correct");
  delta_within(\@returned_peak_times,   $exp_peak_t,   1e-9, "$case: peak times correct");

  my @returned_trough_values = map { $_->{c} } @{$retval->{troughs}};
  my @returned_peak_values   = map { $_->{c} } @{$retval->{peaks}};

  my @exp_trough_values = map { $dm->_get_serum_level($_) } @{$exp_trough_t};
  my @exp_peak_values   = map { $dm->_get_serum_level($_) } @{$exp_peak_t};

  delta_within(\@returned_trough_values, \@exp_trough_values, 1e-9, "$case: trough values correct");
  delta_within(\@returned_peak_values,   \@exp_peak_values,   1e-9, "$case: peak values correct");

  my $npoints = scalar @{$graph};
  note("$case: tests finished, generated $npoints points.");
  return ($npoints); # return number of points in graph so we can compare
}

# generator functions

# sin
#   2 +--------------------------------------------------------------------+
#     |      +  *  * +      +      +   *  *+      +      +    *  *      +  |
# 1.8 |-+      *    *                 *    *                 *    *      +-|
#     |       *      *               *      *               *      *       |
# 1.6 |-+     *      *               *      *               *      *     +-|
#     |       *      *               *      *               *      *       |
# 1.4 |-+     *       *              *       *              *       *    +-|
#     |      *        *             *        *             *        *      |
# 1.2 |-+    *         *            *         *            *         *   +-|
#     |     *          *           *          *           *          *     |
#   1 |-+   *          *           *          *           *          *   +-|
#     |     *           *          *           *          *           *    |
# 0.8 |-+  *            *         *            *         *            *  +-|
#     |    *             *        *             *        *             *   |
# 0.6 |-+ *              *       *              *       *              * +-|
#     |   *               *      *               *      *               *  |
# 0.4 |-+*                *     *                *     *                *+-|
#     |  *                *     *                *     *                *  |
# 0.2 |-*                  *   *                  *   *                  *-|
#     |*     +       +      * *    +       +      +* *   +       +      + *|
#   0 +--------------------------------------------------------------------+
#     0      2       4      6      8       10     12     14      16     18
my $fn_sin = sub { (sin($_[0] - pi/2) + 1) }; # Sin[x-Pi/2]+1

# sinlog
#   4 +--------------------------------------------------------------------+
#     |      +       +      +      +       +      +      +    *  **     +  |
# 3.5 |-+                               ***                 **     *     +-|
#     |                               **  **                *       *      |
#   3 |-+                            *      *              *         *   +-|
#     |                              *       *            *          **    |
#     |         *****               *         *          *             *   |
# 2.5 |-+      *     *             *           *        *               *+-|
#     |       *       **           *            *      *                ***|
#   2 |-+     *        *         **              *    **                 +*|
#     |      *          *       *                 ****                     |
# 1.5 |-+   *            **     *                                        +-|
#     |     *             *** **                                           |
#   1 |-+  *                **                                           +-|
#     |   *                                                                |
#     |  *                                                                 |
# 0.5 |-**                                                               +-|
#     |*                                                                   |
#   0 |*+                                                                +-|
#     |      +       +      +      +       +      +      +       +      +  |
#   -0.5 +--------------------------------------------------------------------+
#     0      2       4      6      8       10     12     14      16     18
my $fn_sin_log = sub { sin($_[0] - pi/2) + log($_[0] + pi/2 + 1) }; # Sin[x - Pi/2]+ Log[x+Pi/2+1]


# test goldensectionpeak algorithm

dies_ok { goldensectionpeak() } "### -- GoldenSectionPeak.Params.UndefinedFun | dies ok if no function provided";
dies_ok { goldensectionpeak($fn_sin) } "### -- GoldenSectionPeak.Params.UndefinedMin | dies ok if no min provided";
dies_ok { goldensectionpeak($fn_sin, pi/2) } "### -- GoldenSectionPeak.Params.UndefinedMax | dies ok if no max provided";
ok(goldensectionpeak($fn_sin, pi/2, pi/2*3), "### -- GoldenSectionPeak.Params.Valid | runs ok if at least fn, min, and max provided");

# asymmetrical placement of starting points
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {type=>'range'});
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {precision=>0.1, type=>'range'});
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {precision=>1e-6, type=>'range'});
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {type=>'domain'});
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {precision=>0.1, type=>'domain'});
gspeaktest('sin - peak asym. between min and max', $fn_sin, pi/2, pi*2, 2.0, {precision=>1e-6, type=>'domain'});
# symmetrical placement of starting points
gspeaktest('sin - peak sym. between min and max', $fn_sin, pi/2, pi/2*3, 2.0, {type=>'range'});
gspeaktest('sin - peak sym. between min and max', $fn_sin, pi/2, pi/2*3, 2.0, {type=>'domain'});

# test cases where there is no peak in interval given to peak search algo
gspeaktest('sin - peak at max', $fn_sin, pi/4, pi/4*3, $fn_sin->(pi/4*3), {type=>'range'} );
gspeaktest('sin - peak at max', $fn_sin, pi/4, pi/4*3, $fn_sin->(pi/4*3), {type=>'domain'} );
gspeaktest('sin - peak at min', $fn_sin, pi/4*5, pi/4*7, $fn_sin->(pi/4*5), {type=>'range'} );
gspeaktest('sin - peak at min', $fn_sin, pi/4*5, pi/4*7, $fn_sin->(pi/4*5), {type=>'domain'} );

# check defaults

plotparamstest('defaults', undef,
    [
      ['t_start', 0],
      ['t_end', 24*4],
      ['aspect', 2],
      ['autoscale', undef],
      ['gamma', 0.8],
      ['gstype', 'domain'],
      ['gsprecision', 1/3600],
      ['min_step', 1/60],
      ['max_step', 6],
      ['seekpeak', 1],
      ['noseek_ndose_threshold', undef],
      ['relax', sqrt(2)],
      ['trim', undef],
      ['threshold', 0.001],
      # the following are not passed but derived
      ['n_doses', 4],
      ['final_dose_time', 72]
    ]
  );

# check that we can change params and they are passed from wrapper to algo

plotparamstest('custom values',
    {
      t_start => 12,
      t_end => 56,
      aspect_ratio => 4,
      gamma => 0.9,
      min_step => 0.1,
      max_step => 2,
      relax => 1.5,
      trim => 1,
      trim_threshold => 0.02,
      seekpeak => 0,
      seekpeak_type => 'range',
      seekpeak_precision => 0.1,
      noseek_ndose_threshold => 12
    },
    [
      ['t_start', 12],
      ['t_end', 56],
      ['aspect', 4],
      ['autoscale', undef],
      ['gamma', 0.9],
      ['gstype', 'range'],
      ['gsprecision', 0.1],
      ['min_step', 0.1],
      ['max_step', 2],
      ['seekpeak', 0],
      ['noseek_ndose_threshold', 12],
      ['relax', 1.5],
      ['trim', 1],
      ['threshold', 0.02],
      # the following are not passed but derived
      ['n_doses', 2],         # this is trimmed by setting t_start ad t_end
      ['final_dose_time', 48] # so is this
    ]
  );

# test using the sin function

my $sin_exp_peaks = {t => [pi, 3*pi, 5*pi], c => [2,2,2]};

graphtest('sin - end of administration at dose time', $fn_sin, [0, 2*pi, 4*pi], $sin_exp_peaks->{t}, $sin_exp_peaks->{c});
graphtest('sin - end of administration past dose time', $fn_sin, [0+1, 2*pi+1, 4*pi+1], $sin_exp_peaks->{t}, $sin_exp_peaks->{c});
graphtest('sin - end of administration coincident with peak time', $fn_sin, [pi, 3*pi, 5*pi], $sin_exp_peaks->{t}, $sin_exp_peaks->{c});
graphtest('sin - peak just after end of administration', $fn_sin, [pi-0.2, 3*pi-0.2, 5*pi-0.2], $sin_exp_peaks->{t}, $sin_exp_peaks->{c});

# test using the sin + log function

my $sinlog_exp_peaks = {t => [3.312397662906319, 9.507664840434543, 15.762535809662053],  # solved in wolfram
                        c => [2.757548064168587, 3.4879906463848194, 3.907232119649442]}; # e.g. FindMaximum[Sin[x - Pi/2] + Log[x + Pi/2 + 1], {x, 5 * Pi}]

graphtest('sinlog - end of administration at dose time', $fn_sin_log,
    [0, 2*pi, 4*pi],
    $sinlog_exp_peaks->{t},
    $sinlog_exp_peaks->{c}
  );

graphtest('sinlog - end of administration past dose time', $fn_sin_log,
    [0+1, 2*pi+1, 4*pi+1],
    $sinlog_exp_peaks->{t},
    $sinlog_exp_peaks->{c}
  );

graphtest('sinlog - end of administration coincident with peak time', $fn_sin_log,
    $sinlog_exp_peaks->{t},
    $sinlog_exp_peaks->{t},
    $sinlog_exp_peaks->{c}
  );

graphtest('sinlog - peak just after end of administration', $fn_sin_log,
    [pi, 3*pi, 5*pi], # because peaks for this function are later than than for simple sin function
    $sinlog_exp_peaks->{t},
    $sinlog_exp_peaks->{c}
  );

# exampilin tests

my @npoints;

# all doses have a peak
$npoints[0] = drugtest($init_exampilin, "exampilin tau = 24h", 24,
  [4.90471432804659, 28.7134390008927, 52.698524322931, 76.6902625998285],
  [0, 24, 48, 72, 72+24], {test_skip_no_duplicates => 1});
# same but with collinear point trimming on
$npoints[1] = drugtest($init_exampilin, "exampilin tau = 24h, trimmed", 24,
  [4.90471432804659, 28.7134390008927, 52.698524322931, 76.6902625998285],
  [0, 24, 48, 72, 72+24], {trim => 1, test_skip_no_duplicates => 1});
# aggressive trimming
$npoints[2] = drugtest($init_exampilin, "exampilin tau = 24h, aggressive trimmed", 24,
  [4.90471432804659, 28.7134390008927, 52.698524322931, 76.6902625998285],
  [0, 24, 48, 72, 72+24], {trim => 1, trim_threshold => 0.01, test_skip_no_duplicates => 1});
# very aggressive trimming
$npoints[3] = drugtest($init_exampilin, "exampilin tau = 24h, very aggressive trimmed", 24,
  [4.90471432804659, 28.7134390008927, 52.698524322931, 76.6902625998285],
  [0, 24, 48, 72, 72+24], {trim => 1, trim_threshold => 0.1, test_skip_no_duplicates => 1});

# technically the tests below should be '>=', but we are pretty sure we will trim some points
note("exampillin tau = 24, comparing number of points generated");
note("exampillin tau = 24, generated $npoints[0] points.");
cmp_ok($npoints[0] - $npoints[1], '>', 0, 'exampillin tau = 24, trimmed ' . ($npoints[0] - $npoints[1]) . ' points.');
cmp_ok($npoints[1] - $npoints[2], '>', 0, 'exampillin tau = 24, aggressive trimmed ' . ($npoints[1] - $npoints[2]) . ' more points.');
cmp_ok($npoints[2] - $npoints[3], '>', 0, 'exampillin tau = 24, very aggressive trimmed ' . ($npoints[2] - $npoints[3]) . ' more points.');

# first two doses do not reach a peak before next dose administration starts
$npoints[4] = drugtest($init_exampilin, "exampilin tau = 4h", 4,
  [4, 8, 10.9172489249583, 14.4368090095821],
  [0, 4, 8, 12, 12+24], {test_skip_no_duplicates => 1});
# same but with collinear point trimming on
$npoints[5] = drugtest($init_exampilin, "exampilin tau = 4h, trimmed", 4,
  [4, 8, 10.9172489249583, 14.4368090095821],
  [0, 4, 8, 12, 12+24], {trim => 1, test_skip_no_duplicates => 1});
# aggressive trimming
$npoints[6] = drugtest($init_exampilin, "exampilin tau = 4h, aggressive trimmed", 4,
  [4, 8, 10.9172489249583, 14.4368090095821],
  [0, 4, 8, 12, 12+24], {trim => 1, trim_threshold => 0.01, test_skip_no_duplicates => 1});
# very aggressive trimming
$npoints[7] = drugtest($init_exampilin, "exampilin tau = 4h, very aggressive trimmed", 4,
  [4, 8, 10.9172489249583, 14.4368090095821],
  [0, 4, 8, 12, 12+24], {trim => 1, trim_threshold => 0.1, test_skip_no_duplicates => 1});

# technically the tests below should be '>=', but we are pretty sure we will trim some points
note("exampillin tau = 4, comparing number of points generated");
note("exampillin tau = 4, generated $npoints[0] points.");
cmp_ok($npoints[4] - $npoints[5], '>', 0, 'exampillin tau = 4, trimmed ' . ($npoints[4] - $npoints[5]) . ' points.');
cmp_ok($npoints[5] - $npoints[6], '>', 0, 'exampillin tau = 4, aggressive trimmed ' . ($npoints[5] - $npoints[6]) . ' more points.');
cmp_ok($npoints[6] - $npoints[7], '>', 0, 'exampillin tau = 4, very aggressive trimmed ' . ($npoints[6] - $npoints[7]) . ' more points.');

# numerically fix the number of points generated

cmp_ok($npoints[0], '==', 290, "exampillin tau = 24, trimmed points generated");
cmp_ok($npoints[1], '==', 249, "exampillin tau = 24, aggressive trimmed points generated");
cmp_ok($npoints[2], '==', 160, "exampillin tau = 24, very aggressive trimmed generated");
cmp_ok($npoints[3], '==', 78, "exampilin tau = 4h points generated");
cmp_ok($npoints[4], '==', 99, "exampilin tau = 4h, trimmed points generated");
cmp_ok($npoints[5], '==', 98, "exampilin tau = 4h, aggressive trimmed points generated");
cmp_ok($npoints[6], '==', 75, "exampilin tau = 4h, very aggressive trimmed points generated");

# amikacin tests - we use amikacin because unlike exampilin it has zero order absorption and peak coincides with end of administration

my $amikacin_expected_peak_t = [
    0.6,
    12.5,
    24.6,
    36.5,
    48.6,
    60.5,
    72.6,
    84.5,
    96.6,
    108.5,
    120.6,
    132.5
  ];

my $amikacin_expected_trough_t = [
    0,
    12,
    24,
    36,
    48,
    60,
    72,
    84,
    96,
    108,
    120,
    132,
    156
  ];

$npoints[8] = drugtest($init_amikacin, "amikacin", 0, # tau doesnt matter for amikacin tests ans is fixed in init fn
  $amikacin_expected_peak_t, $amikacin_expected_trough_t);
$npoints[9] = drugtest($init_amikacin, "amikacin, trimmed", 0,
  $amikacin_expected_peak_t, $amikacin_expected_trough_t, {trim => 1});
$npoints[10] = drugtest($init_amikacin, "amikacin, aggressive trimmed", 0,
  $amikacin_expected_peak_t, $amikacin_expected_trough_t, {trim => 1, trim_threshold => 0.01});
$npoints[11] = drugtest($init_amikacin, "amikacin, very aggressive trimmed", 0,
  $amikacin_expected_peak_t, $amikacin_expected_trough_t, {trim => 1, trim_threshold => 0.1});
$npoints[12] = drugtest($init_amikacin, "amikacin, very aggressive trimmed, maxstep = 1h", 0,
  $amikacin_expected_peak_t, $amikacin_expected_trough_t, {trim => 1, trim_threshold => 0.1, maxstep => 1});

# technically the tests below should be '>=', but we are pretty sure we will trim some points
note("amikacin, comparing number of points generated");
note("amikacin, generated $npoints[8] points.");
cmp_ok($npoints[8] - $npoints[9], '>', 0, 'amikacin, trimmed ' . ($npoints[8] - $npoints[9]) . ' points.');
cmp_ok($npoints[9] - $npoints[10], '>', 0, 'amikacin, aggressive trimmed ' . ($npoints[9] - $npoints[10]) . ' more points.');
cmp_ok($npoints[10] - $npoints[11], '>', 0, 'amikacin, very aggressive trimmed ' . ($npoints[10] - $npoints[11]) . ' more points.');
cmp_ok($npoints[11] - $npoints[12], '<', 0, 'amikacin, very aggressive trimmed ' . ($npoints[12] - $npoints[11]) . ' less points with maxstep = 1.');

# nuerically fix expected number of points returned
cmp_ok($npoints[8], '==', 562, "amikacin number of points generated");
cmp_ok($npoints[9], '==', 351, "amikacin, trimmed number of points generated");
cmp_ok($npoints[10], '==', 184, "amikacin, aggressive trimmed number of points generated");
cmp_ok($npoints[11], '==', 92, "amikacin, very aggressive trimmed number of points generated");
cmp_ok($npoints[12], '==', 190, "amikacin, very aggressive trimmed, maxstep = 1h number of points generated");

# extra tests for graph trimmer

dies_ok  { trim_graph() }                                         "### -- TrimGraph.Params.PlotUndefined | dies ok if no parameters passed";
dies_ok  { trim_graph({t => 1, c=>1}, {cfx => 1, cfy=>2}) }       "### -- TrimGraph.Params.PlotNotArayref | dies ok if graph is not an array ref";
lives_ok { trim_graph([{t => 1, c=>1}]) }                         "### -- TrimGraph.Params.PlotOnly | trim_graph: lives ok if graph passed";
lives_ok { trim_graph([{t => 1, c=>1}], {cfx => 1, cfy=>2}) }     "### -- TrimGraph.Params.PlotWithOptions | trim_graph: lives ok if graph and opts passed";
lives_ok { trim_graph([]) }                                       "### -- TrimGraph.Params.EmptyPlot | trim_graph: lives ok if empty graph";

# done_testing();
