/*
 * This file is part of the Amalthea library.
 * Copyright (C) 2021, 2024, 2026 Eugene 'Vindex' Stulin
 * Distributed under the BSL 1.0 or the GNU LGPL 3.0 or later.
 */

/**
 * Several functions for working with time data and for sleeping.
 */
module amalthea.time;

import core.stdc.time;
import core.sys.posix.signal : timespec;
import core.thread : Thread;

public import amalthea.libcore;
import amalthea.sys : SysException;

import std.conv : to;
import std.datetime;
import std.datetime.systime;
import std.exception : collectException, enforce;
import std.string;


/*******************************************************************************
 * The function returns number of seconds since 1970-01-01.
 * Params:
 *     timeString = String with the format 'YYYY-MM-DD hh:mm:ss'
 *                  or 'YYYY-MM-DDThh:mm:ss'.
 *                  If the string is empty, the function returns current time.
 *                  By default, empty.
 */
long unixTime(string timeString = "") {
    if (timeString.empty) {
        return core.stdc.time.time(null);
    }
    timeString = timeString.replace(" ", "T");
    return SysTime(DateTime.fromISOExtString(timeString)).toUnixTime();
}


/*******************************************************************************
 * Gets a time string from the std.datetime.systime.SysTime object.
 * Returns: String with format "YYYY-MM-DD HH:MM:SS".
 */
string getTimeString(SysTime sysTime) {
    return sysTime.toISOExtString().replace("T"," ").split('.')[0];
}


/*******************************************************************************
 * Gets a time string with the current time.
 * Returns: String with format "YYYY-MM-DD HH:MM:SS".
 */
string getTimeString() {
    return getTimeString(Clock.currTime);
}


/*******************************************************************************
 * Gets a time string by UNIX time (number of seconds).
 * Returns: String with format "YYYY-MM-DD HH:MM:SS".
 */
string getTimeString(long secondsByUnixTime) {
    return getTimeString(SysTime.fromUnixTime(secondsByUnixTime));
}


/*******************************************************************************
 * Gets a time string by timestamp (only seconds from timestamp is used).
 * Returns: String with format "YYYY-MM-DD HH:MM:SS".
 */
string getTimeString(timestamp_t t) {
    return getTimeString(SysTime.fromUnixTime(t.tv_sec));
}


/*******************************************************************************
 * Gets a time string by timestamp content with nanoseconds.
 * Returns: String with format "YYYY-MM-DD HH:MM:SS.NNNNNNNNN".
 */
string getTimeExtString(timestamp_t t) {
    string part1 = getTimeString(SysTime.fromUnixTime(t.tv_sec));
    string nsecs = format!"%09d"(t.tv_nsec);
    return part1 ~ "." ~ nsecs;
}


/*******************************************************************************
 * Gets a string containing the current time with nanoseconds.
 * Returns: String with format "YYYY-MM-DD HH:MM:SS.NNNNNNNNN".
 */
string getTimeExtString() {
    return getTimeExtString(getCurrentTimestamp());
}


/*******************************************************************************
 * UNIX timestamp structure.
 * Corresponds to C structure types 'statx_timestamp' and 'timespec'.
 */
extern(C)
struct timestamp_t {
    /// Seconds since the Epoch (UNIX time).
    long tv_sec;
    /// Nanoseconds since tv_sec.
    uint tv_nsec;
}


extern(C) private int clock_gettime(int __clock_id, timespec *__tp);


/*******************************************************************************
 * Current time as timestamp_t.
 */
timestamp_t getCurrentTimestamp() {
    timespec t;
    enum CLOCK_REALTIME = 0;
    int ret = clock_gettime(CLOCK_REALTIME, &t);
    enforce(ret != 1, new SysException("clock_gettime"));
    timestamp_t ts = { cast(long)t.tv_sec, cast(uint)t.tv_nsec };
    return ts;
}


/*******************************************************************************
 * Transforms UNIX timestamp structure to std.datetime.systime.SysTime.
 * SysTime stores hectonanoseconds, so some of the timestamp info is lost.
 */
SysTime convTimestampToSysTime(timestamp_t t) {
    ulong hnsecs = t.tv_sec * 10_000_000 + t.tv_nsec / 100;
    return SysTime(621_355_968_000_000_000L + hnsecs);
}


/*******************************************************************************
 * Returns string with timezone offset (hours and minutes) like +0300 or -0700.
 */
string getTZOffsetAsString() {
    Duration d = LocalTime().utcOffsetAt(Clock.currTime().stdTime);
    int hours, minutes;
    abs(d).split!("hours", "minutes")(hours, minutes);
    auto offsetFmt = d < Duration.zero ? "-%02d%02d" : "+%02d%02d";
    return format(offsetFmt, hours, minutes);
}


/*******************************************************************************
 * The function makes a delay, expressed in seconds.
 */
void sleep(double seconds) {
    ulong microseconds = (seconds <= 0) ? 0 : to!ulong(seconds * (10^^6));
    Thread.sleep(dur!("usecs")(microseconds));
}


/*******************************************************************************
 * The function makes a delay, expressed in milliseconds.
 */
void msleep(ulong milliseconds) {
    Thread.sleep(dur!("msecs")(milliseconds));
}


/*******************************************************************************
 * The function makes a delay, expressed in microseconds.
 */
void usleep(ulong microseconds) {
    Thread.sleep(dur!("usecs")(microseconds));
}

