#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"
#include "ppport.h"
#include "moment.h"
#include "moment_fmt.h"
#include "moment_parse.h"

typedef enum {
    MOMENT_PARAM_UNKNOWN=0,
    MOMENT_PARAM_YEAR,
    MOMENT_PARAM_MONTH,
    MOMENT_PARAM_DAY,
    MOMENT_PARAM_HOUR,
    MOMENT_PARAM_MINUTE,
    MOMENT_PARAM_SECOND,
    MOMENT_PARAM_NANOSECOND,
    MOMENT_PARAM_OFFSET,
    MOMENT_PARAM_LENIENT,
    MOMENT_PARAM_REDUCED,
    MOMENT_PARAM_EPOCH,
    MOMENT_PARAM_PRECISION,
} moment_param_t;

typedef int64_t I64V;

#if IVSIZE >= 8
# define SvI64V(sv)         (I64V)SvIV(sv)
# define newSVi64v(i64)     newSViv((IV)i64)
# define XSRETURN_I64V(i64) XSRETURN_IV((IV)i64)
#else
# define SvI64V(sv)         (I64V)SvNV(sv)
# define newSVi64v(i64)     newSVnv((NV)i64)
# define XSRETURN_I64V(i64) XSRETURN_NV((NV)i64)
#endif

#ifndef STR_WITH_LEN
#define STR_WITH_LEN(s)  ("" s ""), (sizeof(s)-1)
#endif

#ifndef gv_stashpvs
#define gv_stashpvs(s, flags) gv_stashpvn(STR_WITH_LEN(s), flags)
#endif

#ifndef XSRETURN_BOOL
#define XSRETURN_BOOL(v) STMT_START { ST(0) = boolSV(v); XSRETURN(1); } STMT_END
#endif

#ifndef XSRETURN_SV
#define XSRETURN_SV(sv) STMT_START { ST(0) = sv; XSRETURN(1); } STMT_END
#endif

#ifndef cBOOL
#define cBOOL(cbool) ((cbool) ? (bool)1 : (bool)0)
#endif

#ifndef PERL_UNUSED_VAR
#  define PERL_UNUSED_VAR(x) ((void)x)
#endif

#define MY_CXT_KEY "Time::Moment::_guts" XS_VERSION
typedef struct {
    HV *stash;
} my_cxt_t;

START_MY_CXT

static void
setup_my_cxt(pTHX_ pMY_CXT) {
    MY_CXT.stash = gv_stashpvs("Time::Moment", GV_ADD);
}

static moment_param_t
moment_param(const char *s, const STRLEN len) {
    switch (len) {
        case 3:
            if (memEQ(s, "day", 3))
                return MOMENT_PARAM_DAY;
            break;
        case 4:
            if (memEQ(s, "year", 4))
                return MOMENT_PARAM_YEAR;
            if (memEQ(s, "hour", 4))
                return MOMENT_PARAM_HOUR;
            break;
        case 5:
            if (memEQ(s, "month", 5))
                return MOMENT_PARAM_MONTH;
            if (memEQ(s, "epoch", 5))
                return MOMENT_PARAM_EPOCH;
            break;
        case 6:
            if (memEQ(s, "minute", 6))
                return MOMENT_PARAM_MINUTE;
            if (memEQ(s, "second", 6))
                return MOMENT_PARAM_SECOND;
            if (memEQ(s, "offset", 6))
                return MOMENT_PARAM_OFFSET;
            break;
        case 7:
            if (memEQ(s, "lenient", 7))
                return MOMENT_PARAM_LENIENT;
            if (memEQ(s, "reduced", 7))
                return MOMENT_PARAM_REDUCED;
            break;
        case 9:
            if (memEQ(s, "precision", 9))
                return MOMENT_PARAM_PRECISION;
            break;
        case 10:
            if (memEQ(s, "nanosecond", 10))
                return MOMENT_PARAM_NANOSECOND;
            break;
    }
    return MOMENT_PARAM_UNKNOWN;
}

static moment_param_t
THX_sv_moment_param(pTHX_ SV *sv) {
    const char *str;
    STRLEN len;

    str = SvPV_const(sv, len);
    return moment_param(str, len);
}

static SV *
THX_sv_as_object(pTHX_ SV *sv, const char *name) {
    dSP;
    SV *rv;
    GV *method;
    int count;

    if (!SvROK(sv))
        return NULL;
    rv = SvRV(sv);
    if (!SvOBJECT(rv) || !SvSTASH(rv))
        return NULL;
    if (!(method = gv_fetchmethod(SvSTASH(rv), name)))
        return NULL;

    ENTER;
    SAVETMPS;
    PUSHMARK(SP);
    XPUSHs(sv);
    PUTBACK;
    count = call_sv((SV *)method, G_SCALAR);
    SPAGAIN;
    if (count != 1)
        croak("method call returned %d values, 1 expected", count);
    rv = newSVsv(POPs);
    PUTBACK;
    FREETMPS;
    LEAVE;
    return sv_2mortal(rv);
}

static SV *
THX_sv_2neat(pTHX_ SV *sv) {
    if (sv_isobject(sv)) {
        SV * const rv = SvRV(sv);
        const char *name = sv_reftype(rv, 1);
        const char *type = sv_reftype(rv, 0);
        sv = sv_newmortal();
        sv_setpvf(sv, "%s=%s(0x%p)", name, type, rv);
    }
    return sv;
}

static void
THX_croak_cmp(pTHX_ SV *sv1, SV *sv2, const bool swap, const char *name) {
    if (swap) {
        SV * const tmp = sv1;
        sv1 = sv2;
        sv2 = tmp;
    }
    croak("A %s object can only be compared to another %s object ('%"SVf"', '%"SVf"')",
        name, name, THX_sv_2neat(aTHX_ sv1), THX_sv_2neat(aTHX_ sv2));
}

static SV *
THX_newSVmoment(pTHX_ const moment_t *m, HV *stash) {
    SV *pv = newSVpvn((const char *)m, sizeof(moment_t));
    SV *sv = newRV_noinc(pv);
    sv_bless(sv, stash);
    return sv;
}

static SV *
THX_sv_set_moment(pTHX_ SV *sv, const moment_t *m) {
    if (!SvROK(sv))
        croak("panic: sv_set_moment called with nonreference");
    sv_setpvn_mg(SvRV(sv), (const char *)m, sizeof(moment_t));
    SvTEMP_off(sv);
    return sv;
}

static bool
THX_sv_isa_stash(pTHX_ SV *sv, const char *klass, HV *stash, size_t size) {
    SV *rv;

    SvGETMAGIC(sv);
    if (!SvROK(sv))
        return FALSE;
    rv = SvRV(sv);
    if (!(SvOBJECT(rv) && SvSTASH(rv) && SvPOKp(rv) && SvCUR(rv) == size))
        return FALSE;
    return (SvSTASH(rv) == stash || sv_derived_from(sv, klass));
}

static HV *
THX_stash_constructor(pTHX_ SV *sv, const char *name, STRLEN namelen, HV *stash) {
    const char *pv;
    STRLEN len;

    SvGETMAGIC(sv);
    if (SvROK(sv)) {
        SV * const rv = SvRV(sv);
        if (SvOBJECT(rv) && SvSTASH(rv))
            return SvSTASH(rv);
    }
    pv = SvPV_nomg_const(sv, len);
    if (len == namelen && memEQ(pv, name, namelen))
        return stash;
    return gv_stashpvn(pv, len, GV_ADD);
}

static bool
THX_sv_isa_moment(pTHX_ SV *sv) {
    dMY_CXT;
    return THX_sv_isa_stash(aTHX_ sv, "Time::Moment", MY_CXT.stash, sizeof(moment_t));
}

static moment_t *
THX_sv_2moment_ptr(pTHX_ SV *sv, const char *name) {
    if (!THX_sv_isa_moment(aTHX_ sv))
        croak("%s is not an instance of Time::Moment", name);
    return (moment_t *)SvPVX_const(SvRV(sv));
}

static moment_t
THX_sv_2moment(pTHX_ SV *sv, const char *name) {
    return *THX_sv_2moment_ptr(aTHX_ sv, name);
}

static SV *
THX_sv_2moment_coerce_sv(pTHX_ SV *sv) {
    SV *res;

    if (THX_sv_isa_moment(aTHX_ sv))
        return sv;
    res = THX_sv_as_object(aTHX_ sv, "__as_Time_Moment");
    if(!res || !THX_sv_isa_moment(aTHX_ res))
        croak("Cannot coerce object of type %"SVf" to Time::Moment", THX_sv_2neat(aTHX_ sv));
    return res;
}

#define dSTASH_CONSTRUCTOR(sv, name, dstash) \
    HV * const stash = THX_stash_constructor(aTHX_ sv, STR_WITH_LEN(name), dstash)

#define dSTASH_INVOCANT \
    HV * const stash = SvSTASH(SvRV(ST(0)))

#define dSTASH_CONSTRUCTOR_MOMENT(sv) \
    dMY_CXT; \
    dSTASH_CONSTRUCTOR(sv, "Time::Moment", MY_CXT.stash)

#define newSVmoment(m, stash) \
    THX_newSVmoment(aTHX_ m, stash)

#define sv_set_moment(sv, m) \
    THX_sv_set_moment(aTHX_ sv, m);

#define sv_2moment_ptr(sv, name) \
    THX_sv_2moment_ptr(aTHX_ sv, name)

#define sv_2moment(sv, name) \
    THX_sv_2moment(aTHX_ sv, name)

#define sv_2moment_coerce_sv(sv) \
    THX_sv_2moment_coerce_sv(aTHX_ sv)

#define sv_isa_moment(sv) \
    THX_sv_isa_moment(aTHX_ sv)

#define croak_cmp(sv1, sv2, swap, name) \
    THX_croak_cmp(aTHX_ sv1, sv2, swap, name)

#define sv_moment_param(sv) \
    THX_sv_moment_param(aTHX_ sv)

#define sv_reusable(sv) \
    (SvTEMP(sv) && SvREFCNT(sv) == 1 && SvROK(sv) && SvREFCNT(SvRV(sv)) == 1)

XS(XS_Time_Moment_nil) {
    dVAR; dXSARGS;
    PERL_UNUSED_VAR(items);
    XSRETURN_EMPTY;
}

XS(XS_Time_Moment_stringify) {
    dVAR; dXSARGS;
    if (items < 1)
        croak("Wrong number of arguments to Time::Moment::(\"\"");
    ST(0) = moment_to_string(sv_2moment_ptr(ST(0), "self"), FALSE);
    XSRETURN(1);
}

XS(XS_Time_Moment_ncmp) {
    dVAR; dXSARGS;
    const moment_t *m1, *m2;
    bool swap;
    SV *svm1, *svm2;

    if (items < 3)
        croak("Wrong number of arguments to Time::Moment::(<=>");

    svm1 = ST(0);
    svm2 = ST(1);
    swap = cBOOL(SvTRUE(ST(2)));

    if (!sv_isa_moment(svm2))
        croak_cmp(svm1, svm2, swap, "Time::Moment");
    m1 = sv_2moment_ptr(svm1, "self");
    m2 = sv_2moment_ptr(svm2, "other");
    if (swap) {
        const moment_t *tmp = m1;
        m1 = m2;
        m2 = tmp;
    }
    XSRETURN_IV(moment_compare_instant(m1, m2));
}

#ifdef HAS_GETTIMEOFDAY
static moment_t
THX_moment_now(pTHX_ bool utc) {
    struct timeval tv;
    IV off, sec;

    gettimeofday(&tv, NULL);
    if (utc)
        off = 0;
    else {
        const time_t when = tv.tv_sec;
        struct tm *tm;
#ifdef HAS_LOCALTIME_R
        struct tm tmbuf;
#ifdef LOCALTIME_R_NEEDS_TZSET
        tzset();
#endif
        tm = localtime_r(&when, &tmbuf);
#else
        tm = localtime(&when);
#endif
        if (tm == NULL)
            croak("localtime() failed: %s", Strerror(errno));

        sec = ((1461 * (tm->tm_year - 1) >> 2) + tm->tm_yday - 25202) * 86400
            + tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec;
        off = (sec - tv.tv_sec) / 60;
    }
    return moment_from_epoch(tv.tv_sec, tv.tv_usec * 1000, off);
}
#endif

MODULE = Time::Moment   PACKAGE = Time::Moment

PROTOTYPES: DISABLE

BOOT:
{
    MY_CXT_INIT;
    setup_my_cxt(aTHX_ aMY_CXT);
#if (PERL_REVISION == 5 && PERL_VERSION < 9)
    PL_amagic_generation++;
#endif
    sv_setsv(get_sv("Time::Moment::()", GV_ADD), &PL_sv_yes);
    newXS("Time::Moment::()", XS_Time_Moment_nil, file);
    newXS("Time::Moment::(\"\"", XS_Time_Moment_stringify, file);
    newXS("Time::Moment::(<=>", XS_Time_Moment_ncmp, file);
}

#ifdef USE_ITHREADS

void
CLONE(...)
CODE:
{
    MY_CXT_CLONE;
    setup_my_cxt(aTHX_ aMY_CXT);
    PERL_UNUSED_VAR(items);
}

#endif

moment_t 
new(klass, ...)
    SV *klass
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
    IV year = 1, month = 1, day = 1;
    IV hour = 0, minute = 0, second = 0, ns = 0, offset = 0;
    I32 i;
  CODE:
    if (((items - 1) % 2) != 0)
        croak("Odd number of elements in call to constructor when named parameters were expected");

    for (i = 1; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_YEAR:        year   = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_MONTH:       month  = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_DAY:         day    = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_HOUR:        hour   = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_MINUTE:      minute = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_SECOND:      second = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_NANOSECOND:  ns     = SvIV(ST(i+1)); break;
            case MOMENT_PARAM_OFFSET:      offset = SvIV(ST(i+1)); break;
            default: croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    RETVAL = moment_new(year, month, day, hour, minute, second, ns, offset);
  OUTPUT:
    RETVAL

#ifdef HAS_GETTIMEOFDAY

moment_t
now(klass)
    SV *klass
  ALIAS:
    Time::Moment::now     = 0
    Time::Moment::now_utc = 1
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
  CODE:
    RETVAL = THX_moment_now(aTHX_ !!ix);
  OUTPUT:
    RETVAL

#endif

moment_t 
from_epoch(klass, seconds, ...)
    SV *klass
    SV *seconds
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
    IV precision = 6;
  CODE:
    if (items == 2) {
        if (SvIOK(seconds))
            RETVAL = moment_from_epoch(SvI64V(seconds), 0, 0);
        else
            RETVAL = moment_from_epoch_nv(SvNV(seconds), precision);
    }
    else if (items == 3) {
        RETVAL = moment_from_epoch(SvI64V(seconds), SvIV(ST(2)), 0);
    }
    else {
        SV *nanosecond = NULL;
        I32 i;

        if ((items % 2) != 0)
            croak("Odd number of elements in named parameters");

        for (i = 2; i < items; i += 2) {
            switch (sv_moment_param(ST(i))) {
                case MOMENT_PARAM_NANOSECOND:
                    nanosecond = ST(i+1);
                    break;
                case MOMENT_PARAM_PRECISION:
                    precision = SvIV(ST(i+1));
                    break;
                default:
                    croak("Unrecognised parameter: '%"SVf"'", ST(i));
            }
        }

        if (nanosecond)
            RETVAL = moment_from_epoch(SvI64V(seconds), SvIV(nanosecond), 0);
        else {
            if (SvIOK(seconds))
                RETVAL = moment_from_epoch(SvI64V(seconds), 0, 0);
            else
                RETVAL = moment_from_epoch_nv(SvNV(seconds), precision);
        }
    }
  OUTPUT:
    RETVAL

moment_t
from_string(klass, string, ...)
    SV *klass
    SV *string
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
    bool lenient;
    const char *str;
    STRLEN len;
    I32 i;
  CODE:
    if ((items % 2) != 0)
        croak("Odd number of elements in named parameters");

    lenient = FALSE;
    for (i = 2; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_LENIENT:
                lenient = cBOOL(SvTRUE((ST(i+1))));
                break;
            default: 
                croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    str = SvPV_const(string, len);
    RETVAL = moment_from_string(str, len, lenient);
  OUTPUT:
    RETVAL

moment_t
from_rd(klass, jd, ...)
    SV *klass
    NV jd
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
    NV epoch = 0;
    IV precision = 3;
    IV offset = 0;
    I32 i;
  ALIAS:
    Time::Moment::from_rd  = 0
    Time::Moment::from_jd  = 1
    Time::Moment::from_mjd = 2
  CODE:
    if ((items % 2) != 0)
        croak("Odd number of elements in named parameters");

    switch (ix) {
        case 1: epoch = -1721424.5; break;
        case 2: epoch = 678576;     break;
    }

    for (i = 2; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_PRECISION:
                precision = SvIV(ST(i+1));
                break;
            case MOMENT_PARAM_EPOCH:
                epoch = SvNV(ST(i+1));
                break;
            case MOMENT_PARAM_OFFSET:
                if (ix == 0) {
                    offset = SvIV(ST(i+1));
                    break;
                }
                /* FALLTROUGH */
            default:
                croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    if      (ix == 0) RETVAL = moment_from_rd(jd, epoch, precision, offset);
    else if (ix == 1) RETVAL = moment_from_jd(jd, epoch, precision);
    else              RETVAL = moment_from_mjd(jd, epoch, precision);

  OUTPUT:
    RETVAL

void
from_object(klass, object)
    SV *klass
    SV *object
  PREINIT:
    dSTASH_CONSTRUCTOR_MOMENT(klass);
  CODE:
    /* PERL_UNUSED_VAR() fails on certain compilers because HV * const stash */ 
    if (0) {
        HV * dummy = stash;
        PERL_UNUSED_VAR(dummy);
    }
    XSRETURN_SV(sv_2moment_coerce_sv(object));

moment_t
at_utc(self)
    const moment_t *self
  PREINIT:
    dSTASH_INVOCANT;
  ALIAS:
    Time::Moment::at_utc                 = 0
    Time::Moment::at_midnight            = 1
    Time::Moment::at_noon                = 2
    Time::Moment::at_last_day_of_year    = 3
    Time::Moment::at_last_day_of_quarter = 4
    Time::Moment::at_last_day_of_month   = 5
  CODE:
    switch (ix) {
        case 0: RETVAL = moment_at_utc(self);                   break;
        case 1: RETVAL = moment_at_midnight(self);              break;
        case 2: RETVAL = moment_at_noon(self);                  break;
        case 3: RETVAL = moment_at_last_day_of_year(self);      break;
        case 4: RETVAL = moment_at_last_day_of_quarter(self);   break;
        case 5: RETVAL = moment_at_last_day_of_month(self);     break;
    }
    if (moment_equals(self, &RETVAL))
        XSRETURN(1);
    if (sv_reusable(ST(0))) {
        sv_set_moment(ST(0), &RETVAL);
        XSRETURN(1);
    }
  OUTPUT:
    RETVAL

moment_t
plus_seconds(self, value)
    const moment_t *self
    I64V value
  PREINIT:
    dSTASH_INVOCANT;
  ALIAS:
    Time::Moment::plus_years        = MOMENT_UNIT_YEARS
    Time::Moment::plus_months       = MOMENT_UNIT_MONTHS
    Time::Moment::plus_weeks        = MOMENT_UNIT_WEEKS
    Time::Moment::plus_days         = MOMENT_UNIT_DAYS
    Time::Moment::plus_hours        = MOMENT_UNIT_HOURS
    Time::Moment::plus_minutes      = MOMENT_UNIT_MINUTES
    Time::Moment::plus_seconds      = MOMENT_UNIT_SECONDS
    Time::Moment::plus_milliseconds = MOMENT_UNIT_MILLIS
    Time::Moment::plus_microseconds = MOMENT_UNIT_MICROS
    Time::Moment::plus_nanoseconds  = MOMENT_UNIT_NANOS
  CODE:
    if (value == 0)
        XSRETURN(1);
    RETVAL = moment_plus_unit(self, (moment_unit_t)ix, value);
    if (sv_reusable(ST(0))) {
        sv_set_moment(ST(0), &RETVAL);
        XSRETURN(1);
    }
  OUTPUT:
    RETVAL

moment_t
minus_seconds(self, value)
    const moment_t *self
    I64V value
  PREINIT:
    dSTASH_INVOCANT;
  ALIAS:
    Time::Moment::minus_years        = MOMENT_UNIT_YEARS
    Time::Moment::minus_months       = MOMENT_UNIT_MONTHS
    Time::Moment::minus_weeks        = MOMENT_UNIT_WEEKS
    Time::Moment::minus_days         = MOMENT_UNIT_DAYS
    Time::Moment::minus_hours        = MOMENT_UNIT_HOURS
    Time::Moment::minus_minutes      = MOMENT_UNIT_MINUTES
    Time::Moment::minus_seconds      = MOMENT_UNIT_SECONDS
    Time::Moment::minus_milliseconds = MOMENT_UNIT_MILLIS
    Time::Moment::minus_microseconds = MOMENT_UNIT_MICROS
    Time::Moment::minus_nanoseconds  = MOMENT_UNIT_NANOS
  CODE:
    if (value == 0)
        XSRETURN(1);
    RETVAL = moment_minus_unit(self, (moment_unit_t)ix, value);
    if (sv_reusable(ST(0))) {
        sv_set_moment(ST(0), &RETVAL);
        XSRETURN(1);
    }
  OUTPUT:
    RETVAL

void
delta_years(self, other)
    const moment_t *self
    const moment_t *other
  PREINIT:
    int64_t delta;
  ALIAS:
    Time::Moment::delta_years        = MOMENT_UNIT_YEARS
    Time::Moment::delta_months       = MOMENT_UNIT_MONTHS
    Time::Moment::delta_weeks        = MOMENT_UNIT_WEEKS
    Time::Moment::delta_days         = MOMENT_UNIT_DAYS
    Time::Moment::delta_hours        = MOMENT_UNIT_HOURS
    Time::Moment::delta_minutes      = MOMENT_UNIT_MINUTES
    Time::Moment::delta_seconds      = MOMENT_UNIT_SECONDS
    Time::Moment::delta_milliseconds = MOMENT_UNIT_MILLIS
    Time::Moment::delta_microseconds = MOMENT_UNIT_MICROS
    Time::Moment::delta_nanoseconds  = MOMENT_UNIT_NANOS
  PPCODE:
    delta = moment_delta_unit(self, other, (moment_unit_t)ix);
    XSRETURN_I64V(delta);

void
with(self, adjuster)
    const moment_t *self
    SV *adjuster
  PREINIT:
    I32 count;
    PERL_UNUSED_VAR(self);
  PPCODE:
    SvGETMAGIC(adjuster);
    if (SvROK(adjuster))
        adjuster = SvRV(adjuster);
    if (SvTYPE(adjuster) != SVt_PVCV || SvOBJECT(adjuster))
        croak("Parameter: 'adjuster' is not a CODE reference");
    PUSHMARK(SP);
    SP += 1;
    PUTBACK;
    count = call_sv(adjuster, G_SCALAR);
    if (count != 1)
        croak("Expected one return value from adjuster, got %d elements", count);
    if (!sv_isa_moment(ST(0)))
        croak("Expected an instance of Time::Moment from adjuster, got '%"SVf"'", 
          THX_sv_2neat(aTHX_ ST(0)));
    SPAGAIN;

moment_t
with_year(self, value)
    const moment_t *self
    I64V value
  PREINIT:
    dSTASH_INVOCANT;
  ALIAS:
    Time::Moment::with_year               = MOMENT_FIELD_YEAR
    Time::Moment::with_quarter            = MOMENT_FIELD_QUARTER_OF_YEAR
    Time::Moment::with_month              = MOMENT_FIELD_MONTH_OF_YEAR
    Time::Moment::with_week               = MOMENT_FIELD_WEEK_OF_YEAR
    Time::Moment::with_day_of_year        = MOMENT_FIELD_DAY_OF_YEAR
    Time::Moment::with_day_of_quarter     = MOMENT_FIELD_DAY_OF_QUARTER
    Time::Moment::with_day_of_month       = MOMENT_FIELD_DAY_OF_MONTH
    Time::Moment::with_day_of_week        = MOMENT_FIELD_DAY_OF_WEEK
    Time::Moment::with_hour               = MOMENT_FIELD_HOUR_OF_DAY
    Time::Moment::with_minute             = MOMENT_FIELD_MINUTE_OF_HOUR
    Time::Moment::with_minute_of_day      = MOMENT_FIELD_MINUTE_OF_DAY
    Time::Moment::with_second             = MOMENT_FIELD_SECOND_OF_MINUTE
    Time::Moment::with_second_of_day      = MOMENT_FIELD_SECOND_OF_DAY
    Time::Moment::with_millisecond        = MOMENT_FIELD_MILLI_OF_SECOND
    Time::Moment::with_millisecond_of_day = MOMENT_FIELD_MILLI_OF_DAY
    Time::Moment::with_microsecond        = MOMENT_FIELD_MICRO_OF_SECOND
    Time::Moment::with_microsecond_of_day = MOMENT_FIELD_MICRO_OF_DAY
    Time::Moment::with_nanosecond         = MOMENT_FIELD_NANO_OF_SECOND
    Time::Moment::with_nanosecond_of_day  = MOMENT_FIELD_NANO_OF_DAY
    Time::Moment::with_precision          = MOMENT_FIELD_PRECISION
    Time::Moment::with_rdn                = MOMENT_FIELD_RATA_DIE_DAY
  CODE:
    RETVAL = moment_with_field(self, (moment_component_t)ix, value);
    if (moment_equals(self, &RETVAL))
        XSRETURN(1);
    if (sv_reusable(ST(0))) {
        sv_set_moment(ST(0), &RETVAL);
        XSRETURN(1);
    }
  OUTPUT:
    RETVAL

moment_t
with_offset_same_instant(self, offset)
    const moment_t *self
    IV offset
  PREINIT:
    dSTASH_INVOCANT;
  ALIAS:
    Time::Moment::with_offset_same_instant = 0
    Time::Moment::with_offset_same_local   = 1
  CODE:
    if (ix == 0)
        RETVAL = moment_with_offset_same_instant(self, offset);
    else
        RETVAL = moment_with_offset_same_local(self, offset);

    if (moment_equals(self, &RETVAL))
        XSRETURN(1);
    if (sv_reusable(ST(0))) {
        sv_set_moment(ST(0), &RETVAL);
        XSRETURN(1);
    }
  OUTPUT:
    RETVAL

void
year(self)
    const moment_t *self
  ALIAS:
    Time::Moment::year               =  0
    Time::Moment::quarter            =  1
    Time::Moment::month              =  2
    Time::Moment::week               =  3
    Time::Moment::day_of_year        =  4
    Time::Moment::day_of_quarter     =  5
    Time::Moment::day_of_month       =  6
    Time::Moment::day_of_week        =  7
    Time::Moment::hour               =  8
    Time::Moment::minute             =  9
    Time::Moment::minute_of_day      = 10
    Time::Moment::second             = 11
    Time::Moment::second_of_day      = 12
    Time::Moment::millisecond        = 13
    Time::Moment::millisecond_of_day = 14
    Time::Moment::microsecond        = 15
    Time::Moment::nanosecond         = 16
    Time::Moment::offset             = 17
    Time::Moment::precision          = 18
    Time::Moment::rdn                = 19
  PREINIT:
    IV v = 0;
  PPCODE:
    switch (ix) {
        case  0: v = moment_year(self);                 break;
        case  1: v = moment_quarter(self);              break;
        case  2: v = moment_month(self);                break;
        case  3: v = moment_week(self);                 break;
        case  4: v = moment_day_of_year(self);          break;
        case  5: v = moment_day_of_quarter(self);       break;
        case  6: v = moment_day_of_month(self);         break;
        case  7: v = moment_day_of_week(self);          break;
        case  8: v = moment_hour(self);                 break;
        case  9: v = moment_minute(self);               break;
        case 10: v = moment_minute_of_day(self);        break;
        case 11: v = moment_second(self);               break;
        case 12: v = moment_second_of_day(self);        break;
        case 13: v = moment_millisecond(self);          break;
        case 14: v = moment_millisecond_of_day(self);   break;
        case 15: v = moment_microsecond(self);          break;
        case 16: v = moment_nanosecond(self);           break;
        case 17: v = moment_offset(self);               break;
        case 18: v = moment_precision(self);            break;
        case 19: v = moment_rata_die_day(self);         break;
    }
    XSRETURN_IV(v);

void
epoch(self)
    const moment_t *self
  ALIAS:
    Time::Moment::epoch                 = 0
    Time::Moment::utc_rd_as_seconds     = 1
    Time::Moment::local_rd_as_seconds   = 2
    Time::Moment::microsecond_of_day    = 3
    Time::Moment::nanosecond_of_day     = 4
  PREINIT:
    int64_t v = 0;
  PPCODE:
    switch (ix) {
        case 0: v = moment_epoch(self);                 break;
        case 1: v = moment_instant_rd_seconds(self);    break;
        case 2: v = moment_local_rd_seconds(self);      break;
        case 3: v = moment_microsecond_of_day(self);    break;
        case 4: v = moment_nanosecond_of_day(self);     break;
    }
    XSRETURN_I64V(v);

void
jd(self, ...)
    const moment_t *self
  ALIAS:
    Time::Moment::jd  = 0
    Time::Moment::mjd = 1
    Time::Moment::rd  = 2
  PREINIT:
    NV v = 0;
    IV precision = 3;
    moment_t adjusted;
    I32 i;
  PPCODE:
    if (((items - 1) % 2) != 0)
        croak("Odd number of elements in named parameters");

    for (i = 1; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_PRECISION:
                precision = SvIV(ST(i+1));
                break;
            default:
                croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    adjusted = moment_with_precision(self, precision);
    switch (ix) {
        case 0: v = moment_jd(&adjusted);   break;
        case 1: v = moment_mjd(&adjusted);  break;
        case 2: v = moment_rd(&adjusted);   break;
    }
    XSRETURN_NV(v);

void
length_of_year(self)
    const moment_t *self
  ALIAS:
    Time::Moment::length_of_year      = 0
    Time::Moment::length_of_quarter   = 1
    Time::Moment::length_of_month     = 2
    Time::Moment::length_of_week_year = 3
  PREINIT:
    IV v = 0;
  PPCODE:
    switch (ix) {
        case 0: v = moment_length_of_year(self);        break;
        case 1: v = moment_length_of_quarter(self);     break;
        case 2: v = moment_length_of_month(self);       break;
        case 3: v = moment_length_of_week_year(self);   break;
    }
    XSRETURN_IV(v);

void
utc_rd_values(self)
    const moment_t *self
  ALIAS:
    Time::Moment::utc_rd_values     = 0
    Time::Moment::local_rd_values   = 1
  PREINIT:
    IV rdn, sod, nos;
  PPCODE:
    if (ix == 0)
        moment_to_instant_rd_values(self, &rdn, &sod, &nos);
    else
        moment_to_local_rd_values(self, &rdn, &sod, &nos);
    EXTEND(SP, 3);
    mPUSHi(rdn);
    mPUSHi(sod);
    mPUSHi(nos);
    XSRETURN(3);

void
compare(self, other, ...)
    const moment_t *self
    const moment_t *other
  PREINIT:
    IV precision = 9;
    I32 i;
    IV r;
  PPCODE:
    if ((items % 2) != 0)
        croak("Odd number of elements in named parameters");

    for (i = 2; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_PRECISION:
                precision = SvIV(ST(i+1));
                break;
            default:
                croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    if (precision == 9)
        r = moment_compare_instant(self, other);
    else
        r = moment_compare_precision(self, other, precision);
    XSRETURN_IV(r);

void
is_equal(self, other)
    const moment_t *self
    const moment_t *other
  ALIAS:
    Time::Moment::is_equal  = 0
    Time::Moment::is_before = 1
    Time::Moment::is_after  = 2
  PREINIT:
    bool v = FALSE;
  PPCODE:
    switch (ix) {
        case 0: v = moment_compare_instant(self, other) == 0; break;
        case 1: v = moment_compare_instant(self, other) < 0;  break;
        case 2: v = moment_compare_instant(self, other) > 0;  break;
    }
    XSRETURN_BOOL(v);

void
is_leap_year(self)
    const moment_t *self
  PPCODE:
    XSRETURN_BOOL(moment_is_leap_year(self));

void
strftime(self, format)
    const moment_t *self
    SV *format
  PREINIT:
    STRLEN len;
    const char *str;
    SV *ret;
  PPCODE:
    str = SvPV_const(format, len);
    ret = moment_strftime(self, str, len);
    if (SvUTF8(format))
        SvUTF8_on(ret);
    XSRETURN_SV(ret);

void
to_string(self, ...)
    const moment_t *self
  PREINIT:
    bool reduced;
    I32 i;
  PPCODE:
    if (((items - 1) % 2) != 0)
        croak("Odd number of elements in named parameters");

    reduced = FALSE;
    for (i = 1; i < items; i += 2) {
        switch (sv_moment_param(ST(i))) {
            case MOMENT_PARAM_REDUCED:
                reduced = cBOOL(SvTRUE((ST(i+1))));
                break;
            default: 
                croak("Unrecognised parameter: '%"SVf"'", ST(i));
        }
    }
    XSRETURN_SV(moment_to_string(self, reduced));


MODULE = Time::Moment  PACKAGE = Time::Moment::Internal

PROTOTYPES: DISABLE

void
western_easter_sunday(year)
    IV year
  PPCODE:
    XSRETURN_IV(moment_internal_western_easter(year));

void
orthodox_easter_sunday(year)
    IV year
  PPCODE:
    XSRETURN_IV(moment_internal_orthodox_easter(year));