#pragma once

#define LIB8STATIC __attribute__((unused)) static inline
#define GET_MILLIS millis

/// Return the current seconds since boot in a 16-bit value.  Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint16_t seconds16()
{
    uint32_t ms = GET_MILLIS();
    uint16_t s16;
    s16 = ms / 1000;
    return s16;
}

/// Return the current minutes since boot in a 16-bit value.  Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint16_t minutes16()
{
    uint32_t ms = GET_MILLIS();
    uint16_t m16;
    m16 = (ms / (60000L)) & 0xFFFF;
    return m16;
}

/// Return the current hours since boot in an 8-bit value.  Used as part of the
/// "every N time-periods" mechanism
LIB8STATIC uint8_t hours8()
{
    uint32_t ms = GET_MILLIS();
    uint8_t h8;
    h8 = (ms / (3600000L)) & 0xFF;
    return h8;
}

/// Helper routine to divide a 32-bit value by 1024, returning
/// only the low 16 bits. You'd think this would be just
///   result = (in32 >> 10) & 0xFFFF;
/// and on ARM, that's what you want and all is well.
/// But on AVR that code turns into a loop that executes
/// a four-byte shift ten times: 40 shifts in all, plus loop
/// overhead. This routine gets exactly the same result with
/// just six shifts (vs 40), and no loop overhead.
/// Used to convert millis to 'binary seconds' aka bseconds:
/// one bsecond == 1024 millis.
LIB8STATIC uint16_t div1024_32_16(uint32_t in32)
{
    uint16_t out16;
#if defined(__AVR__)
    asm volatile(
        "  lsr %D[in]  \n\t"
        "  ror %C[in]  \n\t"
        "  ror %B[in]  \n\t"
        "  lsr %D[in]  \n\t"
        "  ror %C[in]  \n\t"
        "  ror %B[in]  \n\t"
        "  mov %B[out],%C[in] \n\t"
        "  mov %A[out],%B[in] \n\t"
        : [in] "+r"(in32),
        [out] "=r"(out16));
#else
    out16 = (in32 >> 10) & 0xFFFF;
#endif
    return out16;
}

/// bseconds16 returns the current time-since-boot in
/// "binary seconds", which are actually 1024/1000 of a
/// second long.
LIB8STATIC uint16_t bseconds16()
{
    uint32_t ms = GET_MILLIS();
    uint16_t s16;
    s16 = div1024_32_16(ms);
    return s16;
}

// Classes to implement "Every N Milliseconds", "Every N Seconds",
// "Every N Minutes", "Every N Hours", and "Every N BSeconds".
#define INSTANTIATE_EVERY_N_TIME_PERIODS(NAME, TIMETYPE, TIMEGETTER) \
    class NAME {                                                     \
    public:                                                          \
        TIMETYPE mPrevTrigger;                                       \
        TIMETYPE mPeriod;                                            \
                                                                     \
        NAME()                                                       \
        {                                                            \
            reset();                                                 \
            mPeriod = 1;                                             \
        };                                                           \
        NAME(TIMETYPE period)                                        \
        {                                                            \
            reset();                                                 \
            setPeriod(period);                                       \
        };                                                           \
        void setPeriod(TIMETYPE period) { mPeriod = period; };       \
        TIMETYPE getTime() { return (TIMETYPE)(TIMEGETTER()); };     \
        TIMETYPE getPeriod() { return mPeriod; };                    \
        TIMETYPE getElapsed() { return getTime() - mPrevTrigger; }   \
        TIMETYPE getRemaining() { return mPeriod - getElapsed(); }   \
        TIMETYPE getLastTriggerTime() { return mPrevTrigger; }       \
        bool ready()                                                 \
        {                                                            \
            bool isReady = (getElapsed() >= mPeriod);                \
            if (isReady) {                                           \
                reset();                                             \
            }                                                        \
            return isReady;                                          \
        }                                                            \
        void reset() { mPrevTrigger = getTime(); };                  \
        void trigger() { mPrevTrigger = getTime() - mPeriod; };      \
                                                                     \
        operator bool() { return ready(); }                          \
    };
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNMillis, uint32_t, GET_MILLIS);
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNSeconds, uint16_t, seconds16);
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNBSeconds, uint16_t, bseconds16);
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNMinutes, uint16_t, minutes16);
INSTANTIATE_EVERY_N_TIME_PERIODS(CEveryNHours, uint8_t, hours8);

#define CONCAT_HELPER(x, y) x##y
#define CONCAT_MACRO(x, y) CONCAT_HELPER(x, y)
#define EVERY_N_MILLIS(N) EVERY_N_MILLIS_I(CONCAT_MACRO(PER, __COUNTER__), N)
#define EVERY_N_MILLIS_I(NAME, N) \
    static CEveryNMillis NAME(N); \
    if (NAME)
#define EVERY_N_SECONDS(N) EVERY_N_SECONDS_I(CONCAT_MACRO(PER, __COUNTER__), N)
#define EVERY_N_SECONDS_I(NAME, N) \
    static CEveryNSeconds NAME(N); \
    if (NAME)
#define EVERY_N_BSECONDS(N) EVERY_N_BSECONDS_I(CONCAT_MACRO(PER, __COUNTER__), N)
#define EVERY_N_BSECONDS_I(NAME, N) \
    static CEveryNBSeconds NAME(N); \
    if (NAME)
#define EVERY_N_MINUTES(N) EVERY_N_MINUTES_I(CONCAT_MACRO(PER, __COUNTER__), N)
#define EVERY_N_MINUTES_I(NAME, N) \
    static CEveryNMinutes NAME(N); \
    if (NAME)
#define EVERY_N_HOURS(N) EVERY_N_HOURS_I(CONCAT_MACRO(PER, __COUNTER__), N)
#define EVERY_N_HOURS_I(NAME, N) \
    static CEveryNHours NAME(N); \
    if (NAME)
