#ifndef CHRONO_IO_H
#define CHRONO_IO_H

// The MIT License (MIT)
//
// Copyright (c) 2016 Howard Hinnant
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// Our apologies.  When the previous paragraph was written, lowercase had not yet
// been invented (that woud involve another several millennia of evolution).
// We did not mean to shout.

#include <chrono>
#include <cstddef>
#include <cstdint>
#include <iosfwd>
#include <ratio>
#include <string>
#include <type_traits>

namespace date
{

namespace detail
{

#if __cplusplus >= 201402

template <class CharT, std::size_t N>
class string_literal
{
    CharT p_[N];

public:
    using const_iterator = const CharT*;

    string_literal(string_literal const&) = default;
    string_literal& operator=(string_literal const&) = delete;

    template <std::size_t N1 = 2,
              class = std::enable_if_t<N1 == N>>
    constexpr string_literal(CharT c) noexcept
        : p_{c}
    {
    }

    constexpr string_literal(const CharT(&a)[N]) noexcept
        : p_{}
    {
        for (std::size_t i = 0; i < N; ++i)
            p_[i] = a[i];
    }

    template <class U = CharT, class = std::enable_if_t<1 < sizeof(U)>>
    constexpr string_literal(const char(&a)[N]) noexcept
        : p_{}
    {
        for (std::size_t i = 0; i < N; ++i)
            p_[i] = a[i];
    }

    template <class CharT2, class = std::enable_if_t<!std::is_same<CharT2, CharT>{}>>
    constexpr string_literal(string_literal<CharT2, N> const& a) noexcept
        : p_{}
    {
        for (std::size_t i = 0; i < N; ++i)
            p_[i] = a[i];
    }

    template <std::size_t N1, std::size_t N2,
              class = std::enable_if_t<N1 + N2 - 1 == N>>
    constexpr string_literal(const string_literal<CharT, N1>& x,
                             const string_literal<CharT, N2>& y) noexcept
        : p_{}
    {
        std::size_t i = 0;
        for (; i < N1-1; ++i)
            p_[i] = x[i];
        for (std::size_t j = 0; j < N2; ++j, ++i)
            p_[i] = y[j];
    }

    constexpr const CharT* data() const noexcept {return p_;}
    constexpr std::size_t size() const noexcept {return N-1;}

    constexpr const_iterator begin() const noexcept {return p_;}
    constexpr const_iterator end()   const noexcept {return p_ + N-1;}

    constexpr CharT const& operator[](std::size_t n) const noexcept
    {
        return p_[n];
    }

    template <class Traits>
    friend
    std::basic_ostream<CharT, Traits>&
    operator<<(std::basic_ostream<CharT, Traits>& os, const string_literal& s)
    {
        return os << s.p_;
    }
};

template <class CharT1, class CharT2, std::size_t N1, std::size_t N2>
constexpr
inline
string_literal<std::conditional_t<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>,
               N1 + N2 - 1>
operator+(const string_literal<CharT1, N1>& x, const string_literal<CharT2, N2>& y) noexcept
{
    using CharT = std::conditional_t<sizeof(CharT2) <= sizeof(CharT1), CharT1, CharT2>;
    return string_literal<CharT, N1 + N2 - 1>{string_literal<CharT, N1>{x},
                                              string_literal<CharT, N2>{y}};
}

template <class CharT, std::size_t N>
constexpr
inline
string_literal<CharT, N>
msl(const CharT(&a)[N]) noexcept
{
    return string_literal<CharT, N>{a};
}

template <class CharT,
          class = std::enable_if_t<std::is_same<CharT, char>{} ||
                                   std::is_same<CharT, wchar_t>{} ||
                                   std::is_same<CharT, char16_t>{} ||
                                   std::is_same<CharT, char32_t>{}>>
constexpr
inline
string_literal<CharT, 2>
msl(CharT c) noexcept
{
    return string_literal<CharT, 2>{c};
}

constexpr
std::size_t
to_string_len(std::intmax_t i)
{
    std::size_t r = 0;
    do
    {
        i /= 10;
        ++r;
    } while (i > 0);
    return r;
}

template <std::intmax_t N>
constexpr
inline
std::enable_if_t
<
    N < 10,
    string_literal<char, to_string_len(N)+1>
>
msl() noexcept
{
    return msl(char(N % 10 + '0'));
}

template <std::intmax_t N>
constexpr
inline
std::enable_if_t
<
    10 <= N,
    string_literal<char, to_string_len(N)+1>
>
msl() noexcept
{
    return msl<N/10>() + msl(char(N % 10 + '0'));
}

template <class CharT, std::intmax_t N, std::intmax_t D>
constexpr
inline
std::enable_if_t
<
    std::ratio<N, D>::type::den != 1,
    string_literal<CharT, to_string_len(std::ratio<N, D>::type::num) +
                          to_string_len(std::ratio<N, D>::type::den) + 4>
>
msl(std::ratio<N, D>) noexcept
{
    using R = typename std::ratio<N, D>::type;
    return msl(CharT{'['}) + msl<R::num>() + msl(CharT{'/'}) +
                             msl<R::den>() + msl(CharT{']'});
}

template <class CharT, std::intmax_t N, std::intmax_t D>
constexpr
inline
std::enable_if_t
<
    std::ratio<N, D>::type::den == 1,
    string_literal<CharT, to_string_len(std::ratio<N, D>::type::num) + 3>
>
msl(std::ratio<N, D>) noexcept
{
    using R = typename std::ratio<N, D>::type;
    return msl(CharT{'['}) + msl<R::num>() + msl(CharT{']'});
}

template <class CharT>
constexpr
inline
auto
msl(std::atto) noexcept
{
    return msl(CharT{'a'});
}

template <class CharT>
constexpr
inline
auto
msl(std::femto) noexcept
{
    return msl(CharT{'f'});
}

template <class CharT>
constexpr
inline
auto
msl(std::pico) noexcept
{
    return msl(CharT{'p'});
}

template <class CharT>
constexpr
inline
auto
msl(std::nano) noexcept
{
    return msl(CharT{'n'});
}

template <class CharT>
constexpr
inline
std::enable_if_t
<
    std::is_same<CharT, char>{},
    string_literal<char, 3>
>
msl(std::micro) noexcept
{
    return string_literal<char, 3>{"\xC2\xB5"};
}

template <class CharT>
constexpr
inline
std::enable_if_t
<
    !std::is_same<CharT, char>{},
    string_literal<CharT, 2>
>
msl(std::micro) noexcept
{
    return string_literal<CharT, 2>{CharT{static_cast<unsigned char>('\xB5')}};
}

template <class CharT>
constexpr
inline
auto
msl(std::milli) noexcept
{
    return msl(CharT{'m'});
}

template <class CharT>
constexpr
inline
auto
msl(std::centi) noexcept
{
    return msl(CharT{'c'});
}

template <class CharT>
constexpr
inline
auto
msl(std::deci) noexcept
{
    return msl(CharT{'d'});
}

template <class CharT>
constexpr
inline
auto
msl(std::deca) noexcept
{
    return string_literal<CharT, 3>{"da"};
}

template <class CharT>
constexpr
inline
auto
msl(std::hecto) noexcept
{
    return msl(CharT{'h'});
}

template <class CharT>
constexpr
inline
auto
msl(std::kilo) noexcept
{
    return msl(CharT{'k'});
}

template <class CharT>
constexpr
inline
auto
msl(std::mega) noexcept
{
    return msl(CharT{'M'});
}

template <class CharT>
constexpr
inline
auto
msl(std::giga) noexcept
{
    return msl(CharT{'G'});
}

template <class CharT>
constexpr
inline
auto
msl(std::tera) noexcept
{
    return msl(CharT{'T'});
}

template <class CharT>
constexpr
inline
auto
msl(std::peta) noexcept
{
    return msl(CharT{'P'});
}

template <class CharT>
constexpr
inline
auto
msl(std::exa) noexcept
{
    return msl(CharT{'E'});
}

template <class CharT, class Period>
constexpr
auto
get_units(Period p)
{
    return msl<CharT>(p) + string_literal<CharT, 2>{"s"};
}

template <class CharT>
constexpr
auto
get_units(std::ratio<1>)
{
    return string_literal<CharT, 2>{"s"};
}

template <class CharT>
constexpr
auto
get_units(std::ratio<60>)
{
    return string_literal<CharT, 4>{"min"};
}

template <class CharT>
constexpr
auto
get_units(std::ratio<3600>)
{
    return string_literal<CharT, 2>{"h"};
}

#else  //  __cplusplus < 201402

inline
std::string
to_string(std::uint64_t x)
{
    return std::to_string(x);
}

template <class CharT>
std::basic_string<CharT>
to_string(std::uint64_t x)
{
    auto y = std::to_string(x);
    return std::basic_string<CharT>(y.begin(), y.end());
}

template <class CharT, std::intmax_t N, std::intmax_t D>
constexpr
inline
typename std::enable_if
<
    std::ratio<N, D>::type::den != 1,
    std::basic_string<CharT>
>::type
msl(std::ratio<N, D>) noexcept
{
    using R = typename std::ratio<N, D>::type;
    return std::basic_string<CharT>(1, '[') + to_string<CharT>(R::num) + CharT{'/'} +
                                              to_string<CharT>(R::den) + CharT{']'};
}

template <class CharT, std::intmax_t N, std::intmax_t D>
constexpr
inline
typename std::enable_if
<
    std::ratio<N, D>::type::den == 1,
    std::basic_string<CharT>
>::type
msl(std::ratio<N, D>) noexcept
{
    using R = typename std::ratio<N, D>::type;
    return std::basic_string<CharT>(1, '[') + to_string<CharT>(R::num) + CharT{']'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::atto) noexcept
{
    return {'a'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::femto) noexcept
{
    return {'f'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::pico) noexcept
{
    return {'p'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::nano) noexcept
{
    return {'n'};
}

template <class CharT>
constexpr
inline
typename std::enable_if
<
    std::is_same<CharT, char>::value,
    std::string
>::type
msl(std::micro) noexcept
{
    return "\xC2\xB5";
}

template <class CharT>
constexpr
inline
typename std::enable_if
<
    !std::is_same<CharT, char>::value,
    std::basic_string<CharT>
>::type
msl(std::micro) noexcept
{
    return {CharT(static_cast<unsigned char>('\xB5'))};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::milli) noexcept
{
    return {'m'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::centi) noexcept
{
    return {'c'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::deci) noexcept
{
    return {'d'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::deca) noexcept
{
    return {'d', 'a'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::hecto) noexcept
{
    return {'h'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::kilo) noexcept
{
    return {'k'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::mega) noexcept
{
    return {'M'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::giga) noexcept
{
    return {'G'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::tera) noexcept
{
    return {'T'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::peta) noexcept
{
    return {'P'};
}

template <class CharT>
constexpr
inline
std::basic_string<CharT>
msl(std::exa) noexcept
{
    return {'E'};
}

template <class CharT, class Period>
std::basic_string<CharT>
get_units(Period p)
{
    return msl<CharT>(p) + CharT{'s'};
}

template <class CharT>
std::basic_string<CharT>
get_units(std::ratio<1>)
{
    return {'s'};
}

template <class CharT>
std::basic_string<CharT>
get_units(std::ratio<60>)
{
    return {'m', 'i', 'n'};
}

template <class CharT>
std::basic_string<CharT>
get_units(std::ratio<3600>)
{
    return {'h'};
}

#endif  //  __cplusplus >= 201402

}  // namespace detail

template <class CharT, class Traits, class Rep, class Period>
inline
std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& os,
           const std::chrono::duration<Rep, Period>& d)
{
    using namespace std::chrono;
    return os << d.count()
              << detail::get_units<CharT>(typename Period::type{});
}

}  // namespace date

#endif  // CHRONO_IO_H