#ifndef CHRONO_UTILITIES_TIMESPAN_H
#define CHRONO_UTILITIES_TIMESPAN_H

#include "../global.h"

#include <cstdint>
#include <functional>
#include <limits>
#include <string>

namespace CppUtilities {

class DateTime;

/*!
 * \brief Specifies the output format.
 * \sa TimeSpan::toString()
 */
enum class TimeSpanOutputFormat {
    Normal, /**< the normal form of specifying a time interval: hh:mm:ss */
    WithMeasures, /**< measures are used, eg.: 34 d 5 h 10 min 7 s 31 ms */
    TotalSeconds, /**< total seconds (as returned by totalSeconds()), eg. 2304.342 */
};

class CPP_UTILITIES_EXPORT TimeSpan {
    friend class DateTime;

public:
    using TickType = std::int64_t;

    explicit constexpr TimeSpan();
    explicit constexpr TimeSpan(TickType ticks);

    static constexpr TimeSpan fromMilliseconds(double milliseconds);
    static constexpr TimeSpan fromSeconds(double seconds);
    static constexpr TimeSpan fromMinutes(double minutes);
    static constexpr TimeSpan fromHours(double hours);
    static constexpr TimeSpan fromDays(double days);
#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
    static constexpr TimeSpan fromMilliseconds(TickType milliseconds);
    static constexpr TimeSpan fromSeconds(TickType seconds);
    static constexpr TimeSpan fromMinutes(TickType minutes);
    static constexpr TimeSpan fromHours(TickType hours);
    static constexpr TimeSpan fromDays(TickType days);
#endif
    static TimeSpan fromString(const std::string &str, char separator = ':');
    static TimeSpan fromString(const char *str, char separator);
    static constexpr TimeSpan negativeInfinity();
    static constexpr TimeSpan infinity();

    TickType &ticks();
    constexpr TickType totalTicks() const;
    constexpr double totalMicroseconds() const;
    constexpr double totalMilliseconds() const;
    constexpr double totalSeconds() const;
    constexpr double totalMinutes() const;
    constexpr double totalHours() const;
    constexpr double totalDays() const;

    constexpr int nanoseconds() const;
    constexpr int microseconds() const;
    constexpr int milliseconds() const;
    constexpr int seconds() const;
    constexpr int minutes() const;
    constexpr int hours() const;
    constexpr int days() const;

    constexpr bool operator==(const TimeSpan &other) const;
    constexpr bool operator!=(const TimeSpan &other) const;
    constexpr bool operator<(const TimeSpan &other) const;
    constexpr bool operator>(const TimeSpan &other) const;
    constexpr bool operator<=(const TimeSpan &other) const;
    constexpr bool operator>=(const TimeSpan &other) const;
    constexpr TimeSpan operator+(const TimeSpan &other) const;
    constexpr TimeSpan operator-(const TimeSpan &other) const;
    constexpr TimeSpan operator*(double factor) const;
    constexpr TimeSpan operator/(double factor) const;
#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
    constexpr TimeSpan operator*(TickType factor) const;
    constexpr TimeSpan operator/(TickType factor) const;
#endif
    constexpr double operator/(TimeSpan other) const;
    TimeSpan &operator+=(const TimeSpan &other);
    TimeSpan &operator-=(const TimeSpan &other);
    TimeSpan &operator*=(double factor);
    TimeSpan &operator/=(double factor);
#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
    TimeSpan &operator*=(TickType factor);
    TimeSpan &operator/=(TickType factor);
#endif

    std::string toString(TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool fullSeconds = false) const;
    void toString(std::string &result, TimeSpanOutputFormat format = TimeSpanOutputFormat::Normal, bool fullSeconds = false) const;
    constexpr bool isNull() const;
    constexpr bool isNegative() const;
    constexpr bool isNegativeInfinity() const;
    constexpr bool isInfinity() const;

    static constexpr TickType nanosecondsPerTick = 100L;
    static constexpr TickType ticksPerMicrosecond = 10L;
    static constexpr TickType ticksPerMillisecond = 10000L;
    static constexpr TickType ticksPerSecond = 10000000L;
    static constexpr TickType ticksPerMinute = 600000000L;
    static constexpr TickType ticksPerHour = 36000000000L;
    static constexpr TickType ticksPerDay = 864000000000L;

private:
    TickType m_ticks;
};

/*!
 * \brief Constructs a new instance of the TimeSpan class with zero ticks.
 */
constexpr inline TimeSpan::TimeSpan()
    : m_ticks(0)
{
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of ticks.
 */
constexpr inline TimeSpan::TimeSpan(TickType ticks)
    : m_ticks(ticks)
{
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of milliseconds.
 */
constexpr inline TimeSpan TimeSpan::fromMilliseconds(double milliseconds)
{
    return TimeSpan(static_cast<TickType>(milliseconds * static_cast<double>(ticksPerMillisecond)));
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of seconds.
 */
constexpr inline TimeSpan TimeSpan::fromSeconds(double seconds)
{
    return TimeSpan(static_cast<TickType>(seconds * static_cast<double>(ticksPerSecond)));
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of minutes.
 */
constexpr inline TimeSpan TimeSpan::fromMinutes(double minutes)
{
    return TimeSpan(static_cast<TickType>(minutes * static_cast<double>(ticksPerMinute)));
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of hours.
 */
constexpr inline TimeSpan TimeSpan::fromHours(double hours)
{
    return TimeSpan(static_cast<TickType>(hours * static_cast<double>(ticksPerHour)));
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of days.
 */
constexpr inline TimeSpan TimeSpan::fromDays(double days)
{
    return TimeSpan(static_cast<TickType>(days * static_cast<double>(ticksPerDay)));
}

#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of milliseconds.
 */
constexpr inline TimeSpan TimeSpan::fromMilliseconds(TickType milliseconds)
{
    return TimeSpan(milliseconds * ticksPerMillisecond);
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of seconds.
 */
constexpr inline TimeSpan TimeSpan::fromSeconds(TickType seconds)
{
    return TimeSpan(seconds * ticksPerSecond);
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of minutes.
 */
constexpr inline TimeSpan TimeSpan::fromMinutes(TickType minutes)
{
    return TimeSpan(minutes * ticksPerMinute);
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of hours.
 */
constexpr inline TimeSpan TimeSpan::fromHours(TickType hours)
{
    return TimeSpan(hours * ticksPerHour);
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the specified number of days.
 */
constexpr inline TimeSpan TimeSpan::fromDays(TickType days)
{
    return TimeSpan(days * ticksPerDay);
}
#endif

/*!
 * \brief Parses the given std::string as TimeSpan.
 * \throws Throws a ConversionException if the specified \a str does not match the expected format.
 *
 * The expected format is "days:hours:minutes:seconds", eg. "5:31:4.521" for 5 hours, 31 minutes
 * and 4.521 seconds. So parts at the front can be omitted and the parts can be fractions. The
 * colon can be changed by specifying another \a separator.
 */
inline TimeSpan TimeSpan::fromString(const std::string &str, char separator)
{
    return TimeSpan::fromString(str.data(), separator);
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the minimal number of ticks.
 */
constexpr inline TimeSpan TimeSpan::negativeInfinity()
{
    return TimeSpan(std::numeric_limits<TickType>::min());
}

/*!
 * \brief Constructs a new instance of the TimeSpan class with the maximal number of ticks.
 */
constexpr inline TimeSpan TimeSpan::infinity()
{
    return TimeSpan(std::numeric_limits<TickType>::max());
}

/*!
 * \brief Returns a mutable reference to the total ticks.
 */
inline TimeSpan::TickType &TimeSpan::ticks()
{
    return m_ticks;
}

/*!
 * \brief Returns the number of ticks that represent the value of the current TimeSpan class.
 */
constexpr inline TimeSpan::TickType TimeSpan::totalTicks() const
{
    return m_ticks;
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional microseconds.
 */
constexpr double TimeSpan::totalMicroseconds() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerMicrosecond);
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional milliseconds.
 */
constexpr inline double TimeSpan::totalMilliseconds() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerMillisecond);
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional seconds.
 */
constexpr inline double TimeSpan::totalSeconds() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerSecond);
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional minutes.
 */
constexpr inline double TimeSpan::totalMinutes() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerMinute);
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional hours.
 */
constexpr inline double TimeSpan::totalHours() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerHour);
}

/*!
 * \brief Returns the value of the current TimeSpan class expressed in whole and fractional days.
 */
constexpr inline double TimeSpan::totalDays() const
{
    return static_cast<double>(m_ticks) / static_cast<double>(ticksPerDay);
}

/*!
 * \brief Returns the nanoseconds component of the time interval represented by the current TimeSpan class.
 * \remarks The accuracy of the TimeSpan class is 100-nanoseconds. Hence the returned value
 *          will always have two zeros at the end (in decimal representation).
 */
constexpr int TimeSpan::nanoseconds() const
{
    return static_cast<int>(m_ticks % 10l * TimeSpan::nanosecondsPerTick);
}

/*!
 * \brief Returns the microseconds component of the time interval represented by the current TimeSpan class.
 */
constexpr int TimeSpan::microseconds() const
{
    return static_cast<int>((m_ticks / ticksPerMicrosecond) % 1000l);
}

/*!
 * \brief Returns the milliseconds component of the time interval represented by the current TimeSpan class.
 */
constexpr inline int TimeSpan::milliseconds() const
{
    return static_cast<int>((m_ticks / ticksPerMillisecond) % 1000l);
}

/*!
 * \brief Returns the seconds component of the time interval represented by the current TimeSpan class.
 */
constexpr inline int TimeSpan::seconds() const
{
    return static_cast<int>((m_ticks / ticksPerSecond) % 60l);
}

/*!
 * \brief Returns the minutes component of the time interval represented by the current TimeSpan class.
 */
constexpr inline int TimeSpan::minutes() const
{
    return static_cast<int>((m_ticks / ticksPerMinute) % 60l);
}

/*!
 * \brief Returns the hours component of the time interval represented by the current TimeSpan class.
 */
constexpr inline int TimeSpan::hours() const
{
    return static_cast<int>((m_ticks / ticksPerHour) % 24l);
}

/*!
 * \brief Returns the days component of the time interval represented by the current TimeSpan class.
 */
constexpr inline int TimeSpan::days() const
{
    return static_cast<int>((m_ticks / ticksPerDay));
}

/*!
 * \brief Indicates whether two TimeSpan instances are equal.
 */
constexpr inline bool TimeSpan::operator==(const TimeSpan &other) const
{
    return m_ticks == other.m_ticks;
}

/*!
 * \brief Indicates whether two TimeSpan instances are not equal.
 */
constexpr inline bool TimeSpan::operator!=(const TimeSpan &other) const
{
    return m_ticks != other.m_ticks;
}

/*!
 * \brief Indicates whether a specified TimeSpan is less than another specified TimeSpan.
 */
constexpr inline bool TimeSpan::operator<(const TimeSpan &other) const
{
    return m_ticks < other.m_ticks;
}

/*!
 * \brief Indicates whether a specified TimeSpan is greater than another specified TimeSpan.
 */
constexpr inline bool TimeSpan::operator>(const TimeSpan &other) const
{
    return m_ticks > other.m_ticks;
}

/*!
 * \brief Indicates whether a specified TimeSpan is less or equal than another specified TimeSpan.
 */
constexpr inline bool TimeSpan::operator<=(const TimeSpan &other) const
{
    return m_ticks <= other.m_ticks;
}

/*!
 * \brief Indicates whether a specified TimeSpan is greater or equal than another specified TimeSpan.
 */
constexpr inline bool TimeSpan::operator>=(const TimeSpan &other) const
{
    return m_ticks >= other.m_ticks;
}

/*!
 * \brief Adds two TimeSpan instances.
 */
constexpr inline TimeSpan TimeSpan::operator+(const TimeSpan &other) const
{
    return TimeSpan(m_ticks + other.m_ticks);
}

/*!
 * \brief Subtracts one TimeSpan instance from another.
 */
constexpr inline TimeSpan TimeSpan::operator-(const TimeSpan &other) const
{
    return TimeSpan(m_ticks - other.m_ticks);
}

/*!
 * \brief Multiplies a TimeSpan by the specified \a factor.
 */
constexpr inline TimeSpan TimeSpan::operator*(double factor) const
{
    return TimeSpan(static_cast<std::int64_t>(static_cast<double>(m_ticks) * factor));
}

/*!
 * \brief Divides a TimeSpan by the specified \a factor.
 */
constexpr inline TimeSpan TimeSpan::operator/(double factor) const
{
    return TimeSpan(static_cast<std::int64_t>(static_cast<double>(m_ticks) / factor));
}

#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
/*!
 * \brief Multiplies a TimeSpan by the specified \a factor.
 */
constexpr inline TimeSpan TimeSpan::operator*(std::int64_t factor) const
{
    return TimeSpan(m_ticks * factor);
}

/*!
 * \brief Divides a TimeSpan by the specified \a factor.
 */
constexpr inline TimeSpan TimeSpan::operator/(std::int64_t factor) const
{
    return TimeSpan(m_ticks / factor);
}
#endif

/*!
 * \brief Computes the ratio between two TimeSpan instances.
 */
constexpr inline double TimeSpan::operator/(TimeSpan other) const
{
    return static_cast<double>(m_ticks) / static_cast<double>(other.m_ticks);
}

/*!
 * \brief Adds another TimeSpan to the current instance.
 */
inline TimeSpan &TimeSpan::operator+=(const TimeSpan &other)
{
    m_ticks += other.m_ticks;
    return *this;
}

/*!
 * \brief Subtracts another TimeSpan from the current instance.
 */
inline TimeSpan &TimeSpan::operator-=(const TimeSpan &other)
{
    m_ticks -= other.m_ticks;
    return *this;
}

/*!
 * \brief Multiplies the current instance by the specified \a factor.
 */
inline TimeSpan &TimeSpan::operator*=(double factor)
{
    m_ticks = static_cast<std::int64_t>(static_cast<double>(m_ticks) * factor);
    return *this;
}

/*!
 * \brief Divides the current instance by the specified \a factor.
 */
inline TimeSpan &TimeSpan::operator/=(double factor)
{
    m_ticks = static_cast<std::int64_t>(static_cast<double>(m_ticks) / factor);
    return *this;
}

#ifdef CHRONO_UTILITIES_TIMESPAN_INTEGER_SCALE_OVERLOADS
/*!
 * \brief Multiplies the current instance by the specified \a factor.
 */
inline TimeSpan &TimeSpan::operator*=(std::int64_t factor)
{
    m_ticks *= factor;
    return *this;
}

/*!
 * \brief Divides the current instance by the specified \a factor.
 */
inline TimeSpan &TimeSpan::operator/=(std::int64_t factor)
{
    m_ticks /= factor;
    return *this;
}
#endif

/*!
 * \brief Converts the value of the current TimeSpan object to its equivalent std::string representation
 *        according the given \a format.
 *
 * If \a fullSeconds is true the time interval will be rounded to full seconds.
 */
inline std::string TimeSpan::toString(TimeSpanOutputFormat format, bool fullSeconds) const
{
    std::string result;
    toString(result, format, fullSeconds);
    return result;
}

/*!
 * \brief Returns true if the time interval represented by the current TimeSpan class is null.
 */
constexpr inline bool TimeSpan::isNull() const
{
    return m_ticks == 0;
}

/*!
 * \brief Returns true if the time interval represented by the current TimeSpan class is negative.
 */
constexpr inline bool TimeSpan::isNegative() const
{
    return m_ticks < 0;
}

/*!
 * \brief Returns whether the time interval represented by the current instance is the smallest representable TimeSpan.
 */
constexpr inline bool TimeSpan::isNegativeInfinity() const
{
    return m_ticks == std::numeric_limits<decltype(m_ticks)>::min();
}

/*!
 * \brief Returns whether the time interval represented by the current instance is the longest representable TimeSpan.
 */
constexpr inline bool TimeSpan::isInfinity() const
{
    return m_ticks == std::numeric_limits<decltype(m_ticks)>::max();
}
} // namespace CppUtilities

namespace std {
/// \brief Computes the hash for the CppUtilities::TimeSpan instance.
template <> struct hash<CppUtilities::TimeSpan> {
    inline size_t operator()(const CppUtilities::TimeSpan &timeSpan) const
    {
        return hash<decltype(timeSpan.totalTicks())>()(timeSpan.totalTicks());
    }
};
} // namespace std

#endif // CHRONO_UTILITIES_TIMESPAN_H
