//////////////////////////////////////////////////////////////////////////////
// Copyright 2026  Monterey Bay Aquarium Research Institute                 //
// Monterey Bay Aquarium Research Institute Proprietary Information.        //
// All rights reserved.                                                     //
//////////////////////////////////////////////////////////////////////////////
#ifndef LARS_UTILS_HPP  // include guard
#define LARS_UTILS_HPP

#include <cmath>
#include <ctime>
#include <chrono>
#include <thread>
#include <string>
#include <sstream>
#include <iomanip>
#include <iostream>

namespace lars
{
    //////////////////////////////////////////////////////////////////
    // Function: get_timestamp
    // return an <epoch: https://en.wikipedia.org/wiki/Unix_time>
    // time-stamp in seconds
    inline double get_timestamp()
    {
        auto ts = std::chrono::system_clock::now();
        std::chrono::duration<double> epoch_time = ts.time_since_epoch();

        return epoch_time.count();
    }

    //////////////////////////////////////////////////////////////////
    // Function: get_timestamp_us
    // return an <epoch: https://en.wikipedia.org/wiki/Unix_time>
    // time-stamp in microseconds
    inline uint64_t get_timestamp_us()
    {
        uint64_t ts_us = std::chrono::duration_cast<std::chrono::microseconds>
            (std::chrono::system_clock::now().time_since_epoch()).count();

        return ts_us;
    }

    //////////////////////////////////////////////////////////////////
    // Function: get_timestamp_ms
    // return an <epoch: https://en.wikipedia.org/wiki/Unix_time>
    // time-stamp in milliseconds
    inline uint64_t get_timestamp_ms()
    {
        uint64_t ts_ms = std::chrono::duration_cast<std::chrono::milliseconds>
            (std::chrono::system_clock::now().time_since_epoch()).count();

        return ts_ms;
    }

    //////////////////////////////////////////////////////////////////
    // Function: timestamp_to_iso8601
    // convert an <epoch: https://en.wikipedia.org/wiki/Unix_time>
    // time-stamp to an <ISO8601: https://en.wikipedia.org/wiki/ISO_8601>
    // string
    inline std::string timestamp_to_iso8601(double ts)
    {
        char buf[64];
        std::time_t t = static_cast<time_t>(ts);
        double ipart;

        // get the milliseconds from the ts
        int ms = (int)(1000.0 * std::modf(ts, &ipart));

        // create a formatted time stamp string
        std::strftime(buf, 64, "%Y-%m-%dT%H:%M:%S", std::gmtime(&t));

        // add the milliseconds to the string
        std::stringstream ss;

        ss << std::string(buf);
        ss << "." << std::setfill('0') << std::setw(3) << ms << "Z";

        return ss.str();
    }

    //////////////////////////////////////////////////////////////////
    // Function: get_iso8601_timestamp
    // return an <epoch: https://en.wikipedia.org/wiki/Unix_time>
    // time-stamp string using the
    // <ISO8601: https://en.wikipedia.org/wiki/ISO_8601> format
    inline std::string get_iso8601_timestamp()
    {
        return timestamp_to_iso8601(get_timestamp());
    }

    //////////////////////////////////////////////////////////////////
    // Function: get_timestamp_string
    // return a file name friendly time stamp string
    inline std::string get_timestamp_string()
    {
        // grab the time
        auto now = std::chrono::system_clock::now();

        // convert the time to a time_t type
        std::time_t now_t = std::chrono::system_clock::to_time_t(now);

        // create a buffer with YYYY-MM-DD_HH.MM.SS.SSS format
        // note: not using std::put_time as it was broken until GCC 5.0,
        // best to use strftime for portability
        char ts_buff[100];
        std::strftime(ts_buff, sizeof(ts_buff), "%Y-%m-%d_%H.%M.%S",
                      std::localtime(&now_t));

        // get the fractional seconds
        auto ts = now.time_since_epoch();
        auto ts_ms = std::chrono::duration_cast<std::chrono::milliseconds>(ts);
        std::size_t fractional_seconds = ts_ms.count() % 1000;

        // create a formatted time stamp string
        std::stringstream ss;

        ss << std::string(ts_buff) << "."
           << std::setfill('0') << std::setw(3) << fractional_seconds;

        return ss.str();
    }

    //////////////////////////////////////////////////////////////////
    // Function: sleep_ms
    // suspend the calling thread for ms milliseconds
    inline void sleep_ms(long ms)
    {
        return std::this_thread::sleep_for(std::chrono::milliseconds(ms));
    }

    //////////////////////////////////////////////////////////////////
    // Function: info_msg
    // emit info message with time-stamp
    inline void info_msg(const std::string& msg)
    {
        std::stringstream ss;

        ss << "INFO[" << get_iso8601_timestamp() << "]: " << msg;

        std::cout << ss.str() << std::endl;

        return;
    }

    //////////////////////////////////////////////////////////////////
    // Function: info_msg
    // emit warning message with time-stamp
    inline void warn_msg(const std::string& msg)
    {
        std::stringstream ss;

        ss << "WARN[" << get_iso8601_timestamp() << "]: " << msg;

        std::cout << ss.str() << std::endl;

        return;
    }

    //////////////////////////////////////////////////////////////////
    // Function: err_msg
    // emit warning message with time-stamp
    inline void err_msg(const std::string& msg)
    {
        std::stringstream ss;

        ss << "ERR[" << get_iso8601_timestamp() << "]: " << msg;

        std::cout << ss.str() << std::endl;

        return;
    }

    //////////////////////////////////////////////////////////////////////////
    // data extraction functions
    // note: functions expecting data encoded in little-endian format

    static inline int16_t extract_i16(uint8_t* data, int offset)
    {
        // big-endian
    //    return (int16_t)( (data[offset + 1] << 0) |
    //                      (data[offset + 0] << 8) );

        // little-endian
        return (int16_t)( (data[offset + 0] << 0) |
                        (data[offset + 1] << 8) );
    }

    static inline uint16_t extract_u16(uint8_t* data, int offset)
    {
        // big-endian
    //    return (uint16_t)( (data[offset + 1] << 0) |
    //                       (data[offset + 0] << 8) );

        // little-endian
        return (uint16_t)( (data[offset + 0] << 0) |
                        (data[offset + 1] << 8) );
    }

    static inline int32_t extract_i32(uint8_t* data, int offset)
    {
        // big-endian
    //    return (int32_t)( (data[offset + 3] << 0) |
    //                      (data[offset + 2] << 8) |
    //                      (data[offset + 1] << 16) |
    //                      (data[offset + 0] << 24) );

        // little-endian
        return (int32_t)( (data[offset + 0] << 0) |
                        (data[offset + 1] << 8) |
                        (data[offset + 2] << 16) |
                        (data[offset + 3] << 24) );
    }

    static inline uint32_t extract_u32(uint8_t* data, int offset)
    {
        // big-endian
    //    return (uint32_t)( (data[offset + 3] << 0) |
    //                       (data[offset + 2] << 8) |
    //                       (data[offset + 1] << 16) |
    //                       (data[offset + 0] << 24) );

        // little-endian
        return (uint32_t)( (data[offset + 0] << 0) |
                        (data[offset + 1] << 8) |
                        (data[offset + 2] << 16) |
                        (data[offset + 3] << 24) );
    }
};

#endif

