#include <luna/Check.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

struct strftime_state
{
    char* const buf;
    const size_t max;
    size_t pos;
};

static bool try_put_char(strftime_state& state, char c)
{
    if (state.pos == state.max) return false;

    state.buf[state.pos++] = c;

    return true;
}

static bool try_put_string(strftime_state& state, const char* s)
{
    if (state.pos + strlen(s) == (state.max - 1)) return false;

    memcpy(state.buf + state.pos, s, strlen(s));

    state.pos += strlen(s);

    return true;
}

static bool try_format(strftime_state& state, const char* format, ...)
{
    va_list ap;
    va_start(ap, format);

    char buf[BUFSIZ];
    vsnprintf(buf, sizeof(buf), format, ap);

    va_end(ap);

    return try_put_string(state, buf);
}

static const char* abday_names[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static const char* day_names[] = { "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" };
static const char* abmon_names[] = {
    "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
static const char* mon_names[] = { "January", "February", "March",     "April",   "May",      "June",
                                   "July",    "August",   "September", "October", "November", "December" };

extern "C"
{
    size_t strftime(char* buf, size_t max, const char* format, const struct tm* tm)
    {
        strftime_state state = { .buf = buf, .max = max, .pos = 0 };

        while (*format)
        {
            if (*format != '%')
            {
                if (!try_put_char(state, *format)) return 0;

                format++;
                continue;
            }
            else
            {
                format++;
                switch (*format)
                {
                case 'a':
                    if (!try_put_string(state, abday_names[tm->tm_wday])) return 0;
                    break;
                case 'A':
                    if (!try_put_string(state, day_names[tm->tm_wday])) return 0;
                    break;
                case 'b':
                    if (!try_put_string(state, abmon_names[tm->tm_mon])) return 0;
                    break;
                case 'B':
                    if (!try_put_string(state, mon_names[tm->tm_mon])) return 0;
                    break;
                case 'c':
                    if (!try_format(state, "%s %s %.2d %.2d:%.2d:%.2d %d", abday_names[tm->tm_wday],
                                    abmon_names[tm->tm_mon], tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec,
                                    tm->tm_year + 1900))
                        return 0;
                    break;
                case 'd':
                    if (!try_format(state, "%.2d", tm->tm_mday)) return 0;
                    break;
                case 'H':
                    if (!try_format(state, "%.2d", tm->tm_hour)) return 0;
                    break;
                case 'I': {
                    int hour = tm->tm_hour % 12;
                    if (hour == 0) hour = 12;
                    if (!try_format(state, "%.2d", hour)) return 0;
                    break;
                }
                case 'j': todo();
                case 'm':
                    if (!try_format(state, "%.2d", tm->tm_mon + 1)) return 0;
                    break;
                case 'M':
                    if (!try_format(state, "%.2d", tm->tm_min)) return 0;
                    break;
                case 'p':
                    if (tm->tm_hour >= 12)
                    {
                        if (!try_put_string(state, "PM")) return 0;
                    }
                    else
                    {
                        if (!try_put_string(state, "AM")) return 0;
                    }
                    break;
                case 'S':
                    if (!try_format(state, "%.2d", tm->tm_sec)) return 0;
                    break;
                case 'x':
                    // Sorry, Americans, but I ain't doing m/d/y.
                    if (!try_format(state, "%.2d/%.2d/%.2d", tm->tm_mday, tm->tm_mon + 1, tm->tm_year + 1900)) return 0;
                    break;
                case 'X':
                    if (!try_format(state, "%.2d:%.2d:%.2d", tm->tm_hour, tm->tm_min, tm->tm_sec)) return 0;
                    break;
                case 'y':
                    if (!try_format(state, "%.2d", (tm->tm_year + 1900) % 100)) return 0;
                    break;
                case 'Y':
                    if (!try_format(state, "%d", tm->tm_year + 1900)) return 0;
                    break;
                default: fprintf(stderr, "strftime: unexpected conversion specifier %%%c\n", *format); exit(255);
                }

                format++;
                continue;
            }
        }

        state.buf[state.pos] = '\0';

        return state.pos;
    }
}