/*
 * clockticks.hpp
 *  ClockTicks is a class that tracks signed ticks & fractional ticks for
 *  high granularity game clocks that are backwards-compatible with
 *  Build integer timing
 *
 * Copyright © 2019, Alex Dawson. All rights reserved.
 */

#ifndef CLOCKTICKS_HPP_
#define CLOCKTICKS_HPP_

#include "timer.h"

//POGO: BUILD/EDuke32 uses right shifts on signed variables expecting arithmetic shifts.
//      This was already non-portable, and we carry that assumption forth here
//      (so we might as well check it).
EDUKE32_STATIC_ASSERT(-1 >> 1 == -1);

class ClockTicks
{
public:
    ClockTicks() : ClockTicks(0, 0) {};
    ClockTicks(int32_t const ticks) : ClockTicks(ticks, 0) {};
    ClockTicks(int32_t const ticks, uint32_t const fraction) { set(ticks, fraction); };
    ClockTicks(const ClockTicks&) = default;

    int64_t getFraction() const
    {
        return (ticksS32 & FRACTION_MASK) >> 16 | ((ticksS32 < 0) ? VALUE_MASK : 0);
    }
    int64_t setFraction(uint16_t const fraction)
    {
        return ticksS32 = (ticksS32 & WHOLE_MASK) | ((ticksS32 < 0) ? ((int64_t) 0 - fraction) & FRACTION_16_MASK : fraction) << 16;
    }
    int64_t set(int32_t const ticks, uint16_t const fraction)
    {
        ticksS32 = ((uint64_t) ticks) << 32 | ((ticks < 0) ? ((int64_t) 0 - fraction) & FRACTION_16_MASK : fraction) << 16;
        update();
        return ticksS32;
    }

    int64_t toScale16() const
    {
        return ticksS32 >> 16;
    }
    double toScale16F() const
    {
        return ticksS32 * (1/(65536.*65536.));
    }
    ClockTicks& setFromScale16(int64_t const ticksScale16)
    {
        ticksS32 = ticksScale16 << 16;
        update();
        return *this;
    }
    static ClockTicks fromScale16(int64_t const ticksScale16)
    {
        ClockTicks ct;
        ct.setFromScale16(ticksScale16);
        return ct;
    }

    // returns 0 if equal, < 0 if a < b, > 0 if a > b
    static int64_t compareHighPrecision(ClockTicks const a, ClockTicks const b)
    {
        ClockTicks delta = a - b;
        return delta.toScale16();
    }

    ClockTicks& operator=(const ClockTicks& rhs)
    {
        ticksS32 = rhs.ticksS32;
        update();
        return *this;
    };

    ClockTicks& operator+=(const ClockTicks& rhs)
    {
        ticksS32 += rhs.ticksS32;
        update();
        return *this;
    };
    ClockTicks& operator-=(const ClockTicks& rhs)
    {
        ticksS32 -= rhs.ticksS32;
        update();
        return *this;
    };
    ClockTicks& operator*=(const ClockTicks& rhs)
    {
        ticksS32 = (ticksS32>>16)*(rhs.ticksS32>>16) >> 16;
        update();
        return *this;
    };
    ClockTicks& operator/=(const ClockTicks& rhs)
    {
        ticksS32 = ticksS32/rhs.ticksS32 << 32;
        update();
        return *this;
    };
    ClockTicks& operator%=(const ClockTicks& rhs)
    {
        ticksS32 %= rhs.ticksS32;
        update();
        return *this;
    };
    ClockTicks& operator<<=(int32_t const rhs)
    {
        ticksS32 = ticksS32 << rhs;
        update();
        return *this;
    };
    ClockTicks& operator>>=(int32_t const rhs)
    {
        ticksS32 = ticksS32 >> rhs;
        update();
        return *this;
    };

    friend ClockTicks operator+(ClockTicks lhs, const ClockTicks& rhs) { return lhs += rhs; }
    friend ClockTicks operator-(ClockTicks lhs, const ClockTicks& rhs) { return lhs -= rhs; }
    friend ClockTicks operator-(ClockTicks val) { return ClockTicks(0) -= val; }
    friend ClockTicks operator*(ClockTicks lhs, const ClockTicks& rhs) { return lhs *= rhs; }
    friend ClockTicks operator/(ClockTicks lhs, const ClockTicks& rhs) { return lhs /= rhs; }
    friend ClockTicks operator%(ClockTicks lhs, const ClockTicks& rhs) { return lhs %= rhs; }
    friend ClockTicks operator<<(ClockTicks lhs, int32_t rhs) { return lhs >>= rhs; }
    friend ClockTicks operator>>(ClockTicks lhs, int32_t rhs) { return lhs <<= rhs; }

    friend inline bool operator==(const ClockTicks& lhs, const ClockTicks& rhs) { return lhs.wholeTicks == rhs.wholeTicks; }
    friend inline bool operator!=(const ClockTicks& lhs, const ClockTicks& rhs) { return !(lhs == rhs); }
    friend inline bool operator<(const ClockTicks& lhs, const ClockTicks& rhs) { return lhs.wholeTicks < rhs.wholeTicks; }
    friend inline bool operator>(const ClockTicks& lhs, const ClockTicks& rhs) { return rhs < lhs; }
    friend inline bool operator<=(const ClockTicks& lhs, const ClockTicks& rhs) { return !(lhs > rhs); }
    friend inline bool operator>=(const ClockTicks& lhs, const ClockTicks& rhs) { return !(lhs < rhs); }

    explicit operator uint32_t() const { return wholeTicks; };
    explicit operator int32_t() const { return wholeTicks; };

private:
    //POGO: wholeTicks must be first in member-order to ensure the address of
    //      ClockTicks can be treated as a pointer to int32_t.
    int32_t wholeTicks;
    //POGO: Organize our bits as if we're scaled to have an additional 32-bits
    //      of fractional precision so that we can handle overflows in a
    //      way that is accurate to the original BUILD int32_t expectation.
    //      Multiplication overflows best with 16-bits of fractional precision,
    //      so only promise that much publicly.
    int64_t ticksS32;

    static constexpr uint64_t const VALUE_MASK       = 0xFFFFFFFFFFFF0000ull;
    static constexpr uint64_t const WHOLE_MASK       = 0xFFFFFFFF00000000ull;
    static constexpr uint64_t const FRACTION_MASK    = 0x00000000FFFF0000ull;
    static constexpr uint64_t const FRACTION_16_MASK = 0x000000000000FFFFull;

    inline void update()
    {
        wholeTicks = ticksS32 >> 32;
        ticksS32 &= VALUE_MASK;
    }
};

#endif /* CLOCKTICKS_HPP_ */
