/*
 * Copyright 2013 Daniel Warner <contact@danrw.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef TIMESPAN_H_
#define TIMESPAN_H_

#include <cmath>
#include <iomanip>
#include <iostream>
#include <sstream>

#include <keplerian_toolbox/detail/visibility.hpp>

namespace
{
static const long long TicksPerDay = 86400000000LL;
static const long long TicksPerHour = 3600000000LL;
static const long long TicksPerMinute = 60000000LL;
static const long long TicksPerSecond = 1000000LL;
static const long long TicksPerMillisecond = 1000LL;
static const long long TicksPerMicrosecond = 1LL;

static const long long UnixEpoch = 62135596800000000LL;

static const long long MaxValueTicks = 315537897599999999LL;

// 1582-Oct-15
static const long long GregorianStart = 49916304000000000LL;
}

/**
 * @brief Represents a time interval.
 *
 * Represents a time interval (duration/elapsed) that is measured as a positive
 * or negative number of days, hours, minutes, seconds, and fractions
 * of a second.
 */
class KEP_TOOLBOX_DLL_PUBLIC TimeSpan
{
public:
    TimeSpan(long long ticks) : m_ticks(ticks)
    {
    }

    TimeSpan(int hours, int minutes, int seconds)
    {
        CalculateTicks(0, hours, minutes, seconds, 0);
    }

    TimeSpan(int days, int hours, int minutes, int seconds)
    {
        CalculateTicks(days, hours, minutes, seconds, 0);
    }

    TimeSpan(int days, int hours, int minutes, int seconds, int microseconds)
    {
        CalculateTicks(days, hours, minutes, seconds, microseconds);
    }

    TimeSpan Add(const TimeSpan &ts) const
    {
        return TimeSpan(m_ticks + ts.m_ticks);
    }

    TimeSpan Subtract(const TimeSpan &ts) const
    {
        return TimeSpan(m_ticks - ts.m_ticks);
    }

    int Compare(const TimeSpan &ts) const
    {
        int ret = 0;

        if (m_ticks < ts.m_ticks) {
            ret = -1;
        }
        if (m_ticks > ts.m_ticks) {
            ret = 1;
        }
        return ret;
    }

    bool Equals(const TimeSpan &ts) const
    {
        return m_ticks == ts.m_ticks;
    }

    int Days() const
    {
        return static_cast<int>(m_ticks / TicksPerDay);
    }

    int Hours() const
    {
        return static_cast<int>(m_ticks % TicksPerDay / TicksPerHour);
    }

    int Minutes() const
    {
        return static_cast<int>(m_ticks % TicksPerHour / TicksPerMinute);
    }

    int Seconds() const
    {
        return static_cast<int>(m_ticks % TicksPerMinute / TicksPerSecond);
    }

    int Milliseconds() const
    {
        return static_cast<int>(m_ticks % TicksPerSecond / TicksPerMillisecond);
    }

    int Microseconds() const
    {
        return static_cast<int>(m_ticks % TicksPerSecond / TicksPerMicrosecond);
    }

    long long Ticks() const
    {
        return m_ticks;
    }

    double TotalDays() const
    {
        return static_cast<double>(m_ticks) / TicksPerDay;
    }

    double TotalHours() const
    {
        return static_cast<double>(m_ticks) / TicksPerHour;
    }

    double TotalMinutes() const
    {
        return static_cast<double>(m_ticks) / TicksPerMinute;
    }

    double TotalSeconds() const
    {
        return static_cast<double>(m_ticks) / TicksPerSecond;
    }

    double TotalMilliseconds() const
    {
        return static_cast<double>(m_ticks) / TicksPerMillisecond;
    }

    double TotalMicroseconds() const
    {
        return static_cast<double>(m_ticks) / TicksPerMicrosecond;
    }

    std::string ToString() const
    {
        std::stringstream ss;

        ss << std::right << std::setfill('0');

        if (m_ticks < 0) {
            ss << '-';
        }

        if (Days() != 0) {
            ss << std::setw(2) << std::abs(Days()) << '.';
        }

        ss << std::setw(2) << std::abs(Hours()) << ':';
        ss << std::setw(2) << std::abs(Minutes()) << ':';
        ss << std::setw(2) << std::abs(Seconds());

        if (Microseconds() != 0) {
            ss << '.' << std::setw(6) << std::abs(Microseconds());
        }

        return ss.str();
    }

private:
    long long m_ticks;

    void CalculateTicks(int days, int hours, int minutes, int seconds, int microseconds)
    {
        m_ticks = days * TicksPerDay + (hours * 3600LL + minutes * 60LL + seconds) * TicksPerSecond
                  + microseconds * TicksPerMicrosecond;
    }
};

inline std::ostream &operator<<(std::ostream &strm, const TimeSpan &t)
{
    return strm << t.ToString();
}

inline TimeSpan operator+(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return ts1.Add(ts2);
}

inline TimeSpan operator-(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return ts1.Subtract(ts2);
}

inline bool operator==(const TimeSpan &ts1, TimeSpan &ts2)
{
    return ts1.Equals(ts2);
}

inline bool operator>(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return (ts1.Compare(ts2) > 0);
}

inline bool operator>=(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return (ts1.Compare(ts2) >= 0);
}

inline bool operator!=(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return !ts1.Equals(ts2);
}

inline bool operator<(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return (ts1.Compare(ts2) < 0);
}

inline bool operator<=(const TimeSpan &ts1, const TimeSpan &ts2)
{
    return (ts1.Compare(ts2) <= 0);
}

#endif
