#include "time.h"
#include "format.h"

namespace panda { namespace time {

using namespace format;

#define REQCAP(n)                   \
    if (d >= de - n + 1) {          \
        cap *= 2;                   \
        auto len = d - ret.data();  \
        ret.length(len);            \
        d = ret.reserve(cap) + len; \
        de = ret.data() + cap;      \
    }

#define ADD(tag) {                  \
    REQCAP(tag::length);            \
    d = tag::apply(d, dt, 0);   \
}

using mdyr_s_t = exp_t<tag_month, tag_char<'/'>, tag_day, tag_char<'/'>, tag_yr>;

string strftime (string_view format, const datetime& dt) {
    string ret;
    auto len = format.length();
    auto d   = ret.reserve(len > 23 ? (len + 15) : len);
    auto cap = ret.capacity();
    auto de  = (const char*)d + cap;
    auto s   = format.data();
    auto se  = s + len;

    while (s < se) {
        char c = *s++;
        if (c != '%' || s == se) {
            REQCAP(1);
            *d++ = c;
            continue;
        }
        switch (*s++) {
            case 'a': ADD(tag_wday_short);    break;
            case 'A': ADD(tag_wday_long);     break;
            case 'h':
            case 'b': ADD(tag_month_short);   break;
            case 'B': ADD(tag_month_long);    break;
            case 'c': ADD(ansi_c_t);          break;
            case 'C': ADD(tag_century);       break;
            case 'D': ADD(mdyr_s_t);          break;
            case 'd': ADD(tag_day);           break;
            case 'e': ADD(tag_day_spad);      break;
            case 'F': ADD(ymd_t);             break;
            case 'H': ADD(tag_hour);          break;
            case 'I': ADD(tag_hour12);        break;
            case 'j': ADD(tag_yday);          break;
            case 'k': ADD(tag_hour_spad);     break;
            case 'l': ADD(tag_hour12_spad);   break;
            case 'm': ADD(tag_month);         break;
            case 'M': ADD(tag_min);           break;
            case 'n': REQCAP(1); *d++ = '\n'; break;
            case 'p': ADD(tag_AMPM);          break;
            case 'P': ADD(tag_ampm);          break;
            case 'r': ADD(hms12_t);           break;
            case 'R': ADD(hm_t);              break;
            case 's': ADD(tag_epoch);         break;
            case 'S': ADD(tag_sec);           break;
            case 't': REQCAP(1); *d++ = '\t'; break;
            case 'X':
            case 'T': ADD(hms_t);             break;
            case 'u': ADD(tag_ewday);         break;
            case 'w': ADD(tag_c_wday);        break;
            case 'y': ADD(tag_yr);            break;
            case 'Y': ADD(tag_year);          break;
            case 'z': ADD(tag_tzoff_void);    break;
            case 'Z': ADD(tag_tzabbr);        break;
            default: // keep symbol after percent
                REQCAP(1);
                *d++ = *(s-1);
        }
    }

    ret.length(d - ret.data());
    return ret;
}

}}

//%G     The  ISO 8601  week-based  year (see NOTES) with century as a decimal number.  The 4-digit year corresponding to the ISO week number (see %V).  This has the same format and value as %Y, except that if
//       the ISO week number belongs to the previous or next year, that year is used instead. (TZ) (Calculated from tm_year, tm_yday, and tm_wday.)
//%g     Like %G, but without century, that is, with a 2-digit year (00–99). (TZ) (Calculated from tm_year, tm_yday, and tm_wday.)
//%U     The week number of the current year as a decimal number, range 00 to 53, starting with the first Sunday as the first day of week 01.  See also %V and %W.  (Calculated from tm_yday and tm_wday.)
//%V     The ISO 8601 week number (see NOTES) of the current year as a decimal number, range 01 to 53, where week 1 is the first week that has at least 4 days in the new year.  See also %U and %W.  (Calculated
//       from tm_year, tm_yday, and tm_wday.)  (SU)
//%W     The week number of the current year as a decimal number, range 00 to 53, starting with the first Monday as the first day of week 01.  (Calculated from tm_yday and tm_wday.)