/* wrapclock.cc provides access to time related system calls.

Copyright (C) 2009-2022 Free Software Foundation, Inc.
Contributed by Gaius Mulley <gaius.mulley@southwales.ac.uk>.

This file is part of GNU Modula-2.

GNU Modula-2 is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.

GNU Modula-2 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.

Under Section 7 of GPL version 3, you are granted additional
permissions described in the GCC Runtime Library Exception, version
3.1, as published by the Free Software Foundation.

You should have received a copy of the GNU General Public License and
a copy of the GCC Runtime Library Exception along with this program;
see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
<http://www.gnu.org/licenses/>.  */

#include "config.h"
#include <m2rts.h>

#define EXPORT(FUNC) m2iso ## _wrapclock_ ## FUNC
#define M2EXPORT(FUNC) m2iso ## _M2_wrapclock_ ## FUNC
#define M2LIBNAME "m2iso"

#if defined(HAVE_STDLIB_H)
#include "stdlib.h"
#endif

#if defined(HAVE_UNISTD_H)
#include "unistd.h"
#endif

#if defined(HAVE_SYS_TYPES_H)
#include "sys/types.h"
#endif

#if defined(HAVE_SYS_TIME_H)
#include "sys/time.h"
#endif

#if defined(HAVE_TIME_H)
#include "time.h"
#endif

// Conditional inclusion of sys/time.h for gettimeofday
#if !defined(_GLIBCXX_USE_CLOCK_MONOTONIC) && \
    !defined(_GLIBCXX_USE_CLOCK_REALTIME) && \
     defined(_GLIBCXX_USE_GETTIMEOFDAY)
#include <sys/time.h>
#endif

#if defined(_GLIBCXX_USE_CLOCK_GETTIME_SYSCALL)
#include <unistd.h>
#include <sys/syscall.h>
#endif

#if defined(HAVE_MALLOC_H)
#include "malloc.h"
#endif

#if defined(HAVE_LIMITS_H)
#include "limits.h"
#endif

#if !defined(NULL)
#define NULL (void *)0
#endif


/* GetTimeRealtime performs return gettime (CLOCK_REALTIME, ts).
   gettime returns 0 on success and -1 on failure.  If the underlying
   system does not have gettime then GetTimeRealtime returns 1.  */

#if defined(HAVE_STRUCT_TIMESPEC) && defined(_GLIBCXX_USE_CLOCK_REALTIME)
extern "C" int
EXPORT(GetTimeRealtime) (struct timespec *ts)
{
  timespec tp;
#if defined(_GLIBCXX_USE_CLOCK_GETTIME_SYSCALL)
  return syscall (SYS_clock_gettime, CLOCK_REALTIME, ts);
#else
  return clock_gettime (CLOCK_REALTIME, ts);
#endif
}

#else

extern "C" int
EXPORT(GetTimeRealtime) (void *ts)
{
  return 1;
}
#endif

/* SetTimeRealtime performs return settime (CLOCK_REALTIME, ts).
   gettime returns 0 on success and -1 on failure.  If the underlying
   system does not have gettime then GetTimeRealtime returns 1.  */

#if defined(HAVE_STRUCT_TIMESPEC) && defined(_GLIBCXX_USE_CLOCK_REALTIME)
extern "C" int
EXPORT(SetTimeRealtime) (struct timespec *ts)
{
#if defined(_GLIBCXX_USE_CLOCK_SETTIME_SYSCALL)
  return syscall (SYS_clock_settime, CLOCK_REALTIME, ts);
#elif defined(HAVE_CLOCK_SETTIME)
  return clock_settime (CLOCK_REALTIME, ts);
#else
  return 1;
#endif
}

#else

extern "C" int
EXPORT(SetTimeRealtime) (void *ts)
{
  return 1;
}
#endif

/* InitTimespec returns a newly created opaque type.  */

#if defined(HAVE_STRUCT_TIMESPEC)
extern "C" struct timespec *
EXPORT(InitTimespec) (void)
{
#if defined(HAVE_STRUCT_TIMESPEC) && defined(HAVE_MALLOC_H)
  return (struct timespec *)malloc (sizeof (struct timespec));
#else
  return NULL;
#endif
}

#else

extern "C" void *
EXPORT(InitTimespec) (void)
{
  return NULL;
}
#endif

/* KillTimeval deallocates the memory associated with an opaque type.  */

#if defined(HAVE_STRUCT_TIMESPEC)
extern "C" struct timespec *
EXPORT(KillTimespec) (void *ts)
{
#if defined(HAVE_MALLOC_H)
  free (ts);
#endif
  return NULL;
}

#else

extern "C" void *
EXPORT(KillTimespec) (void *ts)
{
  return NULL;
}
#endif

/* GetTimespec retrieves the number of seconds and nanoseconds from the
   timespec.  1 is returned if successful and 0 otherwise.  */

#if defined(HAVE_STRUCT_TIMESPEC)
extern "C" int
EXPORT(GetTimespec) (timespec *ts, unsigned long *sec, unsigned long *nano)
{
#if defined(HAVE_STRUCT_TIMESPEC)
  *sec = ts->tv_sec;
  *nano = ts->tv_nsec;
  return 1;
#else
  return 0;
#endif
}

#else
extern "C" int
EXPORT(GetTimespec) (void *ts, unsigned long *sec, unsigned long *nano)
{
  return 0;
}
#endif

/* SetTimespec sets the number of seconds and nanoseconds into timespec.
   1 is returned if successful and 0 otherwise.  */

#if defined(HAVE_STRUCT_TIMESPEC)
extern "C" int
EXPORT(SetTimespec) (timespec *ts, unsigned long sec, unsigned long nano)
{
#if defined(HAVE_STRUCT_TIMESPEC)
  ts->tv_sec = sec;
  ts->tv_nsec = nano;
  return 1;
#else
  return 0;
#endif
}

#else

extern "C" int
EXPORT(SetTimespec) (void *ts, unsigned long sec, unsigned long nano)
{
  return 0;
}
#endif

extern "C" long int
EXPORT(timezone) (void)
{
#if defined(HAVE_STRUCT_TIMESPEC)
  struct tm result;
  struct timespec ts;

#if defined(HAVE_TM_TM_GMTOFF)
  if (EXPORT(GetTimeRealtime) (&ts) == 0)
    {
      time_t time = ts.tv_sec;
      localtime_r (&time, &result);
      return result.tm_gmtoff;
    }
#endif
#endif
  return 0;
}

/* istimezone returns 1 if timezone in wrapclock.cc can resolve the
   timezone value using the timezone C library call or by using
   clock_gettime, localtime_r and tm_gmtoff.  */

extern "C" int
EXPORT(istimezone) (void)
{
#if defined(HAVE_STRUCT_TIMESPEC)
#if defined(HAVE_TM_TM_GMTOFF)
#if defined(_GLIBCXX_USE_CLOCK_REALTIME)
  return 1;
#endif
#endif
#endif
  return 0;
}

extern "C" int
EXPORT(daylight) (void)
{
#if defined(HAVE_DAYLIGHT)
  return daylight;
#else
  return 0;
#endif
}

/* isdst returns 1 if daylight saving time is currently in effect and
   returns 0 if it is not.  */

extern "C" int
EXPORT(isdst) (void)
{
#if defined(HAVE_STRUCT_TIMESPEC)
  struct tm result;
  struct timespec ts;

  if (EXPORT(GetTimeRealtime) (&ts) == 0)
    {
      time_t time = ts.tv_sec;
      localtime_r (&time, &result);
      return result.tm_isdst;
    }
  else
    return 0;
#else
  return 0;
#endif
}

/* tzname returns the string associated with the local timezone.
   The daylight value is 0 or 1.  The value 0 returns the non
   daylight saving timezone string and the value of 1 returns the
   daylight saving timezone string.  It returns NULL if tzname is
   unavailable.  */

extern "C" char *
EXPORT(tzname) (int daylight)
{
#if defined(HAVE_TZNAME)
  return tzname[daylight];
#else
  return NULL;
#endif
}

/* init - init/finish functions for the module */

/* GNU Modula-2 linking hooks.  */

extern "C" void
M2EXPORT(init) (int, char **, char **)
{
}

extern "C" void
M2EXPORT(fini) (int, char **, char **)
{
}

extern "C" void
M2EXPORT(dep) (void)
{
}

extern "C" void __attribute__((__constructor__))
M2EXPORT(ctor) (void)
{
  m2iso_M2RTS_RegisterModule ("wrapclock", M2LIBNAME,
			      M2EXPORT(init), M2EXPORT(fini),
			      M2EXPORT(dep));
}
