#ifndef INCLUDED_BOBCAT_CLOCKBASE_
#define INCLUDED_BOBCAT_CLOCKBASE_

#include <ostream>

#include <bobcat/clocktypes>

namespace FBB
{

template <typename ChronoClock>
struct ClockBase;

template <typename ChronoClock>
std::ostream &operator<<(std::ostream &out,
                         ClockBase<ChronoClock> const &clock);

template <typename ChronoClock>
struct ClockBase: public ClockTypes
{
    using Clock = ChronoClock;            // std::chrono::system_clock;
    using TimePoint = Clock::time_point;

    friend std::ostream &operator<< <>(std::ostream &out,               // .f
                                       ClockBase<ChronoClock> const &clock);
    private:
        TimePoint d_timePoint;

    public:
        ClockBase(TimePoint const &timePoint); // = now() ?

        template <typename ClockTp>
        ClockBase(ClockBase<ClockTp> const &clock);                     // .f

        template <typename Ratio>
        ClockBase<ChronoClock> &operator+=(
                    std::chrono::duration<int64_t, Ratio> const &amount);

        ClockBase<ChronoClock> &operator+=(int amount);  // amount in seconds

        template <typename Ratio>
        ClockBase<ChronoClock> &operator-=(
                        std::chrono::duration<int64_t, Ratio> const &amount);

        ClockBase<ChronoClock> &operator-=(int amount);  // amount in seconds

        Duration elapsed() const;   // time_since_epoch                 // .f
        long int count() const;                                         // .f

        TimePoint const &timePoint() const; // inserts the same as when 
                                            // inserting the object itself

        template <typename Dest>
        double toDouble() const;

        static TimePoint max();
        static TimePoint min();
        static TimePoint now();
};

template <typename ChronoClock>
inline ClockBase<ChronoClock>::ClockBase(TimePoint const &timePoint)
:
    d_timePoint(timePoint)
{}

template <typename ChronoClock>
template <typename ClockTp>
inline ClockBase<ChronoClock>::ClockBase(ClockBase<ClockTp> const &clock)
:
    d_timePoint(clock.timePoint())
{}

template <typename ChronoClock>
inline ClockBase<ChronoClock>::Duration ClockBase<ChronoClock>::elapsed() const
{
    return d_timePoint.time_since_epoch();
}

template <typename ChronoClock>
inline long int ClockBase<ChronoClock>::count() const
{
    return elapsed().count();
}

template <typename ChronoClock>
inline ChronoClock::time_point const &ClockBase<ChronoClock>::timePoint() const
{
    return d_timePoint;
}

template <typename ChronoClock>
inline std::ostream &operator<<(std::ostream &out, 
                                ClockBase<ChronoClock> const &clock)
{
    return out << clock.d_timePoint;
}

template <typename ChronoClock>
ClockBase<ChronoClock> &ClockBase<ChronoClock>::operator+=(int amount)
{
    d_timePoint += std::chrono::seconds{ amount };
    return *this;
}

template <typename ChronoClock>
template <typename Ratio>
ClockBase<ChronoClock> &ClockBase<ChronoClock>::operator+=(
                        std::chrono::duration<int64_t, Ratio> const &amount)
{
    d_timePoint += amount;
    return *this;
}

template <typename ChronoClock, typename Ratio>
inline ClockBase<ChronoClock> operator+(
                        ClockBase<ChronoClock> const &lhs, 
                        std::chrono::duration<int64_t, Ratio> const &amount)
{
    return ClockBase<ChronoClock>{ lhs } += amount;
}

template <typename ChronoClock, typename Ratio>
inline ClockBase<ChronoClock> operator+(
                        std::chrono::duration<int64_t, Ratio> const &amount,
                        ClockBase<ChronoClock> const &rhs)
{
    return ClockBase<ChronoClock>{ rhs } += amount;
}

template <typename ChronoClock>
inline ClockBase<ChronoClock> operator+(ClockBase<ChronoClock> const &lhs, 
                                       int amount)
{
    return ClockBase<ChronoClock>{ lhs } += amount;
}

template <typename ChronoClock>
inline ClockBase<ChronoClock> operator+(int amount, 
                                      ClockBase<ChronoClock> const &rhs)
{
    return ClockBase<ChronoClock>{ rhs } += amount;
}

template <typename ChronoClock>
ClockBase<ChronoClock> &ClockBase<ChronoClock>::operator-=(int amount)
{
    d_timePoint -= std::chrono::seconds{ amount };
    return *this;
}

template <typename ChronoClock>
template <typename Ratio>
ClockBase<ChronoClock> &ClockBase<ChronoClock>::operator-=(
                            std::chrono::duration<int64_t, Ratio> const &amount)
{
    d_timePoint -= amount;
    return *this;
}

template <typename ChronoClock, typename Ratio>
inline ClockBase<ChronoClock> operator-(
                        ClockBase<ChronoClock> const &lhs, 
                        std::chrono::duration<int64_t, Ratio> const &amount)
{
    return ClockBase<ChronoClock>{ lhs } -= amount;
}

template <typename ChronoClock, typename Ratio>
inline ClockBase<ChronoClock> operator-(ClockBase<ChronoClock> const &lhs, 
                                      int amount)
{
    return ClockBase<ChronoClock>{ lhs } -= amount;
}

// static
template <typename ChronoClock>
inline ClockBase<ChronoClock>::TimePoint ClockBase<ChronoClock>::now()
{
    return ChronoClock::now();
}

// static
template <typename ChronoClock>
inline ClockBase<ChronoClock>::TimePoint ClockBase<ChronoClock>::min()
{
    return TimePoint::min();
}

// static
template <typename ChronoClock>
inline ClockBase<ChronoClock>::TimePoint ClockBase<ChronoClock>::max()
{
    return TimePoint::max();
}

template <typename ChronoClock>
template <typename Dest>
inline double ClockBase<ChronoClock>::toDouble() const
{
    return FBB::toDouble<Dest>(elapsed());
}

} // FBB        
#endif

