/*
  This file is part of CDO. CDO is a collection of Operators to manipulate and analyse Climate model Data.

  Author: Uwe Schulzweida

*/

#include <stdio.h>

#include "cdo_timer.h"
#include "timer.h"

int timer_read, timer_write;

static FILE *rt_unit = stderr;

enum
{
  rt_stat_undef,
  rt_stat_on,
  rt_stat_off
};

// minimal internal time needed to do one measurement

static double tm_shift = 0.0;

// average total overhead for one timer_start & timer_stop pair

static double tm_overhead = 0.0;

constexpr int MAX_TIMER = 128;  // max number of timers allowed

struct RealTimer
{
  int reserved = 0;
  int calls = 0;
  int stat = 0;
  double tot = 0.0;
  double min = 1.e30;
  double max = 0.0;
  double last = 0.0;
  double mark1 = 0.0;
  std::string text;
};

static RealTimer rt[MAX_TIMER];
static int timer_need_init = 1;
static int top_timer = 0;

static double
get_time_val(double mark0)
{
  auto dt = cdo::get_wtime() - mark0;
  dt -= tm_shift;
  return dt;
}

constexpr int ntests = 100;  // tests need about n microsecs

static double
m1(void)
{
  double dt0 = 1.0;
  for (int i = 0; i < ntests; ++i)
    {
      auto mark = cdo::get_wtime();
      auto dt = get_time_val(mark);
      if (dt < dt0) dt0 = dt;
    }

  return dt0;
}

static double
m2(void)
{
  double dt0 = 1.0;
  for (int i = 0; i < ntests; ++i)
    {
      auto mark2 = cdo::get_wtime();
      auto mark1 = cdo::get_wtime();
      auto dt1 = get_time_val(mark1);
      auto dt2 = get_time_val(mark2);
      if (dt2 < dt0) dt0 = dt2;
      if (dt2 < dt1) fprintf(rt_unit, "estimate_overhead: internal error\n");
    }

  return dt0;
}

static void
estimate_overhead(void)
{
  tm_shift = m1();
  tm_overhead = m2();
}

static void
timer_init(void)
{
  estimate_overhead();
  timer_need_init = 0;
}

int
timer_new(const std::string &text)
{
  if (timer_need_init) timer_init();

  if (top_timer > MAX_TIMER)
    {
      for (int it = 0; it < MAX_TIMER; it++) fprintf(stderr, "timer %3d:  %s\n", it, rt[it].text.c_str());

      fprintf(stderr, "timer_new: MAX_TIMER too small!\n");
    }

  auto it = top_timer;
  top_timer++;

  rt[it].reserved = 1;

  if (text.size()) rt[it].text = text;

  return it;
}

static void
timer_check(int it)
{
  if (it < 0 || it > (top_timer - 1)) fprintf(rt_unit, "timer: invalid timer id %d\n", it);
}

double
timer_val(int it)
{
  timer_check(it);

  auto val = rt[it].tot;

  if (rt[it].stat == rt_stat_on) { val += get_time_val(rt[it].mark1); }

  return val;
}

static void
timer_header(void)
{
  fprintf(rt_unit, "\nTimer report:  shift = %g\n", tm_shift);
  fprintf(rt_unit, "timer  calls        t_min    t_average        t_max      t_total  text\n");
}

void
timer_report(void)
{
  timer_header();

  for (int it = 0; it < top_timer; it++)
    {
      auto total = timer_val(it);
      auto avg = rt[it].tot;
      if (rt[it].calls > 0) avg /= rt[it].calls;

      if (rt[it].stat != rt_stat_undef)
        fprintf(rt_unit, "%4d %7d %12.4g %12.4g %12.4g %12.4g  %s\n", it, rt[it].calls, rt[it].min, avg, rt[it].max, total,
                rt[it].text.c_str());
    }
}

void
timer_start(int it)
{
  timer_check(it);

  if (rt[it].stat == rt_stat_on) fprintf(rt_unit, "timer_start: timer_stop call missing\n");

  rt[it].mark1 = cdo::get_wtime();
  rt[it].stat = rt_stat_on;
}

void
timer_stop(int it)
{
  timer_check(it);

  if (rt[it].stat != rt_stat_on)
    {
      if (rt[it].stat == rt_stat_off)
        fprintf(rt_unit, "timer_stop: timer_start call missing\n");
      else
        fprintf(rt_unit, "timer_stop: undefined timer >%s<\n", rt[it].text.c_str());
    }

  auto dt = get_time_val(rt[it].mark1);

  rt[it].last = dt;
  rt[it].tot += dt;
  rt[it].calls++;
  if (dt < rt[it].min) rt[it].min = dt;
  if (dt > rt[it].max) rt[it].max = dt;

  rt[it].stat = rt_stat_off;
}
