/*
 **********************************************************************
 * Copyright (C) Miroslav Lichvar  2018
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of version 2 of the GNU General Public License as
 * published by the Free Software Foundation.
 * 
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 **********************************************************************
 */

#include <local.h>
#include "test.h"

#define LCL_GetSysPrecisionAsQuantum() (1.0e-6)

#include <samplefilt.c>

void
test_unit(void)
{
  NTP_Sample sample_in, sample_out;
  SPF_Instance filter;
  int i, j, k, sum_count, min_samples, max_samples;
  double mean, combine_ratio, sum_err;

  LCL_Initialise();

  memset(&sample_in, 0, sizeof (sample_in));
  memset(&sample_out, 0, sizeof (sample_out));

  for (i = 0; i <= 100; i++) {
    max_samples = random() % 20 + 1;
    min_samples = random() % (max_samples) + 1;
    combine_ratio = TST_GetRandomDouble(0.0, 1.0);

    filter = SPF_CreateInstance(min_samples, max_samples, 2.0, combine_ratio);

    TEST_CHECK(max_samples == SPF_GetMaxSamples(filter));

    for (j = 0, sum_count = 0, sum_err = 0.0; j < 100; j++) {
      DEBUG_LOG("iteration %d/%d", i, j);

      mean = TST_GetRandomDouble(-1.0e3, 1.0e3);
      UTI_ZeroTimespec(&sample_in.time);

      for (k = 0; k < 100; k++) {
        UTI_AddDoubleToTimespec(&sample_in.time, TST_GetRandomDouble(1.0e-1, 1.0e2),
                                &sample_in.time);
        sample_in.offset = mean + TST_GetRandomDouble(-1.0, 1.0);
        sample_in.peer_dispersion = TST_GetRandomDouble(1.0e-4, 2.0e-4);
        sample_in.root_dispersion = TST_GetRandomDouble(1.0e-3, 2.0e-3);
        sample_in.peer_delay = TST_GetRandomDouble(1.0e-2, 2.0e-2);
        sample_in.root_delay = TST_GetRandomDouble(1.0e-1, 2.0e-1);

        TEST_CHECK(SPF_AccumulateSample(filter, &sample_in));
        TEST_CHECK(!SPF_AccumulateSample(filter, &sample_in));

        TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples));

        SPF_GetLastSample(filter, &sample_out);
        TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in)));

        SPF_SlewSamples(filter, &sample_in.time, 0.0, 0.0);
        SPF_CorrectOffset(filter, 0.0);
        SPF_AddDispersion(filter, 0.0);

        if (k + 1 < min_samples)
          TEST_CHECK(!SPF_GetFilteredSample(filter, &sample_out));

        TEST_CHECK(SPF_GetNumberOfSamples(filter) == MIN(k + 1, max_samples));
      }

      if (random() % 10) {
        TEST_CHECK(SPF_GetFilteredSample(filter, &sample_out));

        TEST_CHECK(SPF_GetAvgSampleDispersion(filter) <= 2.0);

        sum_err += sample_out.offset - mean;
        sum_count++;

        TEST_CHECK(UTI_CompareTimespecs(&sample_out.time, &sample_in.time) <= 0 &&
                   sample_out.time.tv_sec >= 0);
        TEST_CHECK(fabs(sample_out.offset - mean) <= 1.0);
        TEST_CHECK(sample_out.peer_dispersion >= 1.0e-4 &&
                   (sample_out.peer_dispersion <= 2.0e-4 || filter->max_samples > 1));
        TEST_CHECK(sample_out.root_dispersion >= 1.0e-3 &&
                   (sample_out.root_dispersion <= 2.0e-3 || filter->max_samples > 1));
        TEST_CHECK(sample_out.peer_delay >= 1.0e-2 &&
                   sample_out.peer_delay <= 2.0e-2);
        TEST_CHECK(sample_out.root_delay >= 1.0e-1 &&
                   sample_out.root_delay <= 2.0e-1);

        if (max_samples == 1)
          TEST_CHECK(!memcmp(&sample_in, &sample_out, sizeof (sample_in)));

      } else {
        SPF_DropSamples(filter);
        TEST_CHECK(filter->last < 0);
      }

      TEST_CHECK(SPF_GetNumberOfSamples(filter) == 0);
    }

    TEST_CHECK(fabs(sum_err / sum_count) < 0.3);

    SPF_DestroyInstance(filter);
  }

  LCL_Finalise();
}
