/*
 * Copyright 2005-2016 ECMWF.
 *
 * This software is licensed under the terms of the Apache Licence Version 2.0
 * which can be obtained at http://www.apache.org/licenses/LICENSE-2.0.
 *
 * In applying this licence, ECMWF does not waive the privileges and immunities granted to it by
 * virtue of its status as an intergovernmental organisation nor does it submit to any jurisdiction.
 */

#include "grib_api_internal.h"

#if GRIB_TIMER

#ifndef GRIB_ON_WINDOWS
# include <sys/time.h>
# include <time.h>
# include <sys/resource.h>
#endif

static grib_timer *timers = NULL;
int value_false=0;
int value_true=1;
#define long64 long

#define ROUND(a)     ((long)((a)+0.5))

double proc_cpu()
{
    struct rusage rup;

    if(getrusage(RUSAGE_SELF,&rup) != -1)
    {
        return (rup.ru_utime.tv_sec + rup.ru_utime.tv_usec / 1000000.0 +
               rup.ru_stime.tv_sec + rup.ru_stime.tv_usec / 1000000.0);
    }
    return clock()/(double)CLOCKS_PER_SEC;
}

static char *timetext(char *pfx,double ds,char *text)
{
    long s = ROUND(ds);
    long x = s;
    long n;
    char sec[20];
    char min[20];
    char hou[20];
    char day[20];

    *text = *sec = *min = *hou = *day = 0;
    if(s)
    {

        if((n = x % 60) != 0) sprintf(sec,"%ld sec ",n);
        x /= 60;
        if((n = x % 60) != 0) sprintf(min,"%ld min ",n);
        x /= 60;
        if((n = x % 24) != 0) sprintf(hou,"%ld hour ",n);
        x /= 24;
        if((n = x) != 0) sprintf(day,"%ld day ",n);

        sprintf(text,"%s%s%s%s%s",pfx,day,hou,min,sec);
    }

    return text;

}

char *timename(double t)
{
    static char buf[80];
    return timetext("",t,buf);
}

grib_timer *grib_get_timer(grib_context* c,const char* name, const char *statname, int elapsed)
{
  grib_timer *t = timers;
  if (!c) c=grib_context_get_default( );

  while(t)
  {
    if(strcmp(name,t->name_) == 0)
      return t;
    t = t->next_;
  }

  t = (grib_timer*)grib_context_malloc_clear(c,sizeof(grib_timer));
  t->name_  = (char*) name;
  t->context =c;
  t->active_  = value_false;
  t->count_   = 0;
  t->timer_   = 0;
  t->total_   = 0;

  t->elapsed_    = elapsed; /* Whether to print CPU usage */
  t->cpu_        = 0;
  t->total_cpu_  = 0;

  t->statname_ = 0;
  if(statname)
    t->statname_ = (char*)statname;

  t->next_    = timers;
  timers      = t;

  return t;
}

int grib_timer_start(grib_timer* t)
{
  int e = gettimeofday(&t->start_,NULL);
  if(e != 0)
    grib_context_log(t->context,GRIB_LOG_WARNING|GRIB_LOG_PERROR,"Error starting timer '%s'",t->name_?t->name_:"unnamed");
  t->active_ = value_true;
  t->cpu_    = proc_cpu();
  return e;
}

int grib_timer_stop(grib_timer* t, long total)
{

  struct timeval stop, diff;
  int e = gettimeofday(&stop,NULL);
  double c = proc_cpu();

  if(e != 0)
    grib_context_log(t->context,GRIB_LOG_WARNING|GRIB_LOG_PERROR,"Error stopping timer '%s'",t->name_?t->name_:"unnamed");

  if(!t->active_)
  {
    grib_context_log(t->context,GRIB_LOG_WARNING,"Stopping non-started timer '%s'",t->name_?t->name_:"unnamed");
    return 1;
  }

  diff.tv_sec  = stop.tv_sec  - t->start_.tv_sec;
  diff.tv_usec = stop.tv_usec - t->start_.tv_usec;
  if (diff.tv_usec < 0)
  {
    diff.tv_sec--;
    diff.tv_usec += 1000000;
  }

  t->timer_ += (double)diff.tv_sec + ((double)diff.tv_usec / 1000000.);
  t->total_ += total;
  t->total_cpu_ += (c - t->cpu_);

  t->active_=value_false;
  t->count_++;

  return e;
}

double grib_timer_value(grib_timer *t)
{
  return t->timer_;
}

const char *bytename(double bytes)
{
    static char *names[] = {
        "","K","M","G","T"                                      };
    double x = bytes;
    int    n = 0;
    static char buf[20];

    while(x>=1024.0)
    {
        x /= 1024.0;
        n++;
    }

    sprintf(buf,"%.2f %s",x,names[n]);

    return buf;
}

void grib_timer_print(grib_timer* t)
{
  char cpu[1024] = "";
  const char *name = t->name_ ? t->name_ : "";
  if(t->timer_>=1)  {
    if(! t->elapsed_ && t->total_cpu_ >= 1.0)
      sprintf(cpu,"cpu: %s",timename(t->total_cpu_));

    if(t->total_ != 0)
    {
      double rate = (double)t->total_/t->timer_;
      char bytes[80];
      sprintf(bytes,"%sbyte(s)",bytename(t->total_));
      grib_context_print(t->context,stdout,"  %s: %s in %s [%sbyte/sec] %s\n",
               name,bytes,timename(t->timer_),bytename(rate),cpu);
    }
    else {
      char* ctimename=timename(t->timer_);
      grib_context_print(t->context,stdout,"  %s: wall: %s%s\n",name,ctimename,cpu);
    }
/*
    if(t->statname_)
      log_statistics(t->statname_,"%ld",(long)t->timer_);
    */
  }
}

void grib_timer_partial_rate(grib_timer *t, double start, long total)
{
  double ptime = t->timer_ - start;
  long ptotal = total;
  const char *name = t->name_?t->name_:"";
  if(ptime>=1)
  {
    double rate = (double)ptotal/ptime;
    char bytes[80];
    sprintf(bytes,"%sbyte(s)",bytename(ptotal));
    grib_context_log(t->context,GRIB_LOG_INFO,"  %s: %s in %s [%sbyte/sec]",
            name,bytes,timename(ptime),bytename(rate));
  }
}

void grib_print_all_timers()
{
  grib_timer *t = timers;
  while(t)
  {
    grib_timer_print(t);
    t = t->next_;
  }
}

void grib_reset_all_timers()
{
  grib_timer *t = timers;
  while(t)
  {
    t->count_  = 0;
    t->timer_  = 0;
    t->active_ = 0;
    t->total_  = 0;
    t->total_cpu_  = 0;
    t = t->next_;
  }
}


/*************************************************
* Timed functions
**************************************************/
/*
int timed_fread(char *buffer, int n, int length, FILE *f, grib_timer *t)
{
  int r = 0;
  long total = 0;
  timer_start(t);
  if((r = fread(buffer,n,length,f)) > 0)
    total = r*n;

  timer_stop(t,total);

  return r;
}


int timed_wind_next(wind* w, FILE *f, char *buffer, long *length, grib_timer *t)
{
  int r = 0;
  long64 total = 0;
  timer_start(t);
  if((r = wind_next(w,f,buffer,length)) == 0)
    total = *length;

  timer_stop(t,total);

  return r;
}

int timed_fwrite(char *buffer, int n, int length, FILE *f, grib_timer *t)
{
  int r = 0;
  long64 total = 0;
  timer_start(t);
  if((r = fwrite(buffer,n,length,f)) > 0)
    total = r*n;
  timer_stop(t,total);

  return r;
}

int timed_fclose(FILE *f, grib_timer *t)
{
  int r = 0;
  timer_start(t);
  r = fclose(f);
  timer_stop(t,0);

  return r;
}

int timed_writetcp(void *data, char *buffer, int n, grib_timer *t)
{
  int r = 0;
  long64 total = 0;
  timer_start(t);
  if((r = writetcp(data,buffer,n)) > 0)
    total = r;
  timer_stop(t,total);

  return r;
}

int timed_readtcp(void *data, char *buffer, int n, grib_timer *t)
{
  int r = 0;
  long64 total = 0;
  timer_start(t);
  if((r = readtcp(data,buffer,n)) > 0)
    total = r;
  timer_stop(t,total);
  return r;
}

int timed_readany(FILE *f, char *buffer, long *length, grib_timer *t)
{
  int e;
  long original = *length;
  long64 total = 0;
  timer_start(t);
  if(((e = _readany(f,buffer,length)) == NOERR) || (e == BUF_TO_SMALL))
    total = (e == BUF_TO_SMALL)?original:*length;
  timer_stop(t,total);

  if(e != NOERR && e != BUF_TO_SMALL && e != EOF)
  {
    if(e == NOT_FOUND_7777)
      marslog(LOG_WARN,"Group 7777 not found by readany",e);
    else
      marslog(LOG_WARN,"Error %d returned by readany",e);
  }

  return e;
}
*/

#else

grib_timer *grib_get_timer(grib_context* c,const char* name, const char *statname, int elapsed)
{
if (!c) c=grib_context_get_default();
grib_context_log(c,GRIB_LOG_FATAL,"grib_get_timer function not available");
return NULL;
} 

int grib_timer_start(grib_timer* t)
{
return 0;
}

int grib_timer_stop(grib_timer* t, long total)
{return 0;}

double grib_timer_value(grib_timer *t)
{
return 0;
}

void grib_timer_print(grib_timer* t)
{return;}


void grib_timer_partial_rate(grib_timer *t, double start, long total)
{ return; }

void grib_print_all_timers()
{return;}

void grib_reset_all_timers()
{return;}


#endif
