#define _GNU_SOURCE

#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <time.h>
#include <sys/time.h>
#include <err.h>
#include <errno.h>
#include <string.h>
#include "sandbox.h"

void        usage(void);
int         first_wday_of_year(int year);
int         last_wday_of_year(int year);
int         total_days_of_year(int year);
time_t      correct_struct_tm(struct tm *tm);
time_t      set_time(const char *time);

const char  *fmt[] = {
    "%H:%M:%S %d %b %Y",
    "%H:%M:%S %Y-%m-%d",
    NULL
};

int
main(int argc, char *argv[])
{
    int             ch, option_index;
    int             leap_flag, gmt_flag;

    const struct option long_options[] = {
        {"set",         required_argument,  NULL,       's'     },
        {"first-day",   required_argument,  NULL,       'f'     },
        {"last-day",    required_argument,  NULL,       'l'     },
        {"total-days",  required_argument,  NULL,       't'     },
        {"is-leap",     required_argument,  &leap_flag,  1      },
        {"help",        no_argument,        NULL,       'h'     },
        {"gmt",         no_argument,        NULL,       'g'     },
        {"unix-time",   no_argument,        NULL,       'u'     },
        {0,             0,                  0,           0      }
    };

    leap_flag = 0;
    gmt_flag = 0;
    option_index = 0;

    while ((ch = getopt_long(argc, argv, "s:f:l:t:guh", long_options,
                    &option_index)) != -1) {
        switch (ch) {
        case 0:
            if (leap_flag == 1) {
                printf("%s\n", total_days_of_year(atoi(optarg)) == 366
                        ? "yes"
                        : "no");
                leap_flag = 0;
            }
            break;

        case 'f':
            printf("%s\n", day_name(first_wday_of_year(atoi(optarg))));
            break;

        case 'l':
            printf("%s\n", day_name(last_wday_of_year(atoi(optarg))));
            break;

        case 't':
            printf("%d\n", total_days_of_year(atoi(optarg)));
            break;

        case 's':
            if (set_time(optarg) != -1)
                warnx("problem in set time\n");
            break;

        case 'u':
            printf("%ld\n", time(NULL));
            break;

        case 'g':
            gmt_flag = 1;
            break;

        case '?':
            break;

        default:
            printf("?? getopt_long() returned 0%o character code ??\n", ch);
        }

        option_index = 0;
    }

    if (argc == 1 || (argc == 2 && gmt_flag)) {
        time_t      t;
        struct tm   *tm;

        t = time(NULL);
        tm = gmt_flag ? gmtime(&t) : localtime(&t);
        printf("%s", asctime(tm));
    }

    exit(EXIT_SUCCESS);
}

time_t
set_time(const char *time)
{
    int             i, found;
    struct tm       tm;
    struct timeval  tv;
    char            *ret;
    time_t          t;

    found = 0;
    for (i = 0; fmt[i] != NULL; i++) {
        ret = strptime(time, fmt[i], &tm);
        if (ret == NULL || *ret != '\0')
            continue;

        found = 1;
        break;
    }
    if (!found) {
        usage();
        /* UNREACHABLE */
    }

    if ((t = mktime(&tm)) == (time_t) -1)
        return -1;

    tv.tv_sec = t;
    tv.tv_usec = 0;

    if (settimeofday(&tv, NULL) == -1)
        return (time_t) -1;

    return t;
}

void
usage(void)
{
    fprintf(stderr, "usage: %s format-string\n",
            program_invocation_short_name);
    fprintf(stderr, "format-string:\n");
    fprintf(stderr, "21:12:05 2026-04-26\n");
    fprintf(stderr, "21:12:05 26 Apr 2026\n");
    exit(EXIT_FAILURE);
}

int
first_wday_of_year(int year)
{
    struct tm tm;

    memset(&tm, 0, sizeof(struct tm));
    tm.tm_year = year - 1900;
    tm.tm_mon = 0;
    tm.tm_mday = 1;

    if (correct_struct_tm(&tm) == -1)
        return -1;

    return tm.tm_wday;
}

int
last_wday_of_year(int year)
{
    struct tm tm;

    memset(&tm, 0, sizeof(struct tm));
    tm.tm_year = year - 1900 + 1;
    tm.tm_mon = 0;
    tm.tm_mday = 0;

    if (correct_struct_tm(&tm) == -1)
        return -1;

    return tm.tm_wday;
}

int
total_days_of_year(int year)
{
    struct tm tm;

    memset(&tm, 0, sizeof(struct tm));
    tm.tm_year = year - 1900 + 1;
    tm.tm_mon = 0;
    tm.tm_mday = 0;

    if (correct_struct_tm(&tm) == -1)
        return -1;

    return tm.tm_yday + 1;
}

time_t
correct_struct_tm(struct tm *tm)
{
    time_t t;

    if ((t = mktime(tm)) == (time_t) -1)
        return (time_t) -1;

    return t;
}
