/*
Copyright (c) 2020, PyData Development Team
All rights reserved.
Distributed under the terms of the BSD Simplified License.
The full license is in the LICENSE file, distributed with this software.
*/

// Conversion routines that are useful for serialization,
// but which don't interact with JSON objects directly

#include "date_conversions.h"
#include <../../../tslibs/src/datetime/np_datetime.h>
#include <../../../tslibs/src/datetime/np_datetime_strings.h>

/*
 * Function: scaleNanosecToUnit
 * -----------------------------
 *
 * Scales an integer value representing time in nanoseconds to provided unit.
 *
 * Mutates the provided value directly. Returns 0 on success, non-zero on error.
 */
int scaleNanosecToUnit(npy_int64 *value, NPY_DATETIMEUNIT unit) {
    switch (unit) {
    case NPY_FR_ns:
        break;
    case NPY_FR_us:
        *value /= 1000LL;
        break;
    case NPY_FR_ms:
        *value /= 1000000LL;
        break;
    case NPY_FR_s:
        *value /= 1000000000LL;
        break;
    default:
        return -1;
    }

    return 0;
}

/* Converts the int64_t representation of a datetime to ISO; mutates len */
char *int64ToIso(int64_t value, NPY_DATETIMEUNIT base, size_t *len) {
    npy_datetimestruct dts;
    int ret_code;

    pandas_datetime_to_datetimestruct(value, NPY_FR_ns, &dts);

    *len = (size_t)get_datetime_iso_8601_strlen(0, base);
    char *result = PyObject_Malloc(*len);

    if (result == NULL) {
        PyErr_NoMemory();
        return NULL;
    }
    // datetime64 is always naive
    ret_code = make_iso_8601_datetime(&dts, result, *len, 0, base);
    if (ret_code != 0) {
        PyErr_SetString(PyExc_ValueError,
                        "Could not convert datetime value to string");
        PyObject_Free(result);
    }

    // Note that get_datetime_iso_8601_strlen just gives a generic size
    // for ISO string conversion, not the actual size used
    *len = strlen(result);
    return result;
}

npy_datetime NpyDateTimeToEpoch(npy_datetime dt, NPY_DATETIMEUNIT base) {
    scaleNanosecToUnit(&dt, base);
    return dt;
}

/* Convert PyDatetime To ISO C-string. mutates len */
char *PyDateTimeToIso(PyObject *obj, NPY_DATETIMEUNIT base,
                      size_t *len) {
    npy_datetimestruct dts;
    int ret;

    ret = convert_pydatetime_to_datetimestruct(obj, &dts);
    if (ret != 0) {
        if (!PyErr_Occurred()) {
            PyErr_SetString(PyExc_ValueError,
                            "Could not convert PyDateTime to numpy datetime");
        }
        return NULL;
    }

    *len = (size_t)get_datetime_iso_8601_strlen(0, base);
    char *result = PyObject_Malloc(*len);
    // Check to see if PyDateTime has a timezone.
    // Don't convert to UTC if it doesn't.
    int is_tz_aware = 0;
    if (PyObject_HasAttrString(obj, "tzinfo")) {
        PyObject *offset = extract_utc_offset(obj);
        if (offset == NULL) {
            PyObject_Free(result);
            return NULL;
        }
        is_tz_aware = offset != Py_None;
        Py_DECREF(offset);
    }
    ret = make_iso_8601_datetime(&dts, result, *len, is_tz_aware, base);

    if (ret != 0) {
        PyErr_SetString(PyExc_ValueError,
                        "Could not convert datetime value to string");
        PyObject_Free(result);
        return NULL;
    }

    // Note that get_datetime_iso_8601_strlen just gives a generic size
    // for ISO string conversion, not the actual size used
    *len = strlen(result);
    return result;
}

npy_datetime PyDateTimeToEpoch(PyObject *dt, NPY_DATETIMEUNIT base) {
    npy_datetimestruct dts;
    int ret;

    ret = convert_pydatetime_to_datetimestruct(dt, &dts);
    if (ret != 0) {
        if (!PyErr_Occurred()) {
            PyErr_SetString(PyExc_ValueError,
                            "Could not convert PyDateTime to numpy datetime");
        }
        // TODO(username): is setting errMsg required?
        // ((JSONObjectEncoder *)tc->encoder)->errorMsg = "";
        // return NULL;
    }

    npy_datetime npy_dt = npy_datetimestruct_to_datetime(NPY_FR_ns, &dts);
    return NpyDateTimeToEpoch(npy_dt, base);
}

/* Converts the int64_t representation of a duration to ISO; mutates len */
char *int64ToIsoDuration(int64_t value, size_t *len) {
    pandas_timedeltastruct tds;
    int ret_code;

    pandas_timedelta_to_timedeltastruct(value, NPY_FR_ns, &tds);

    // Max theoretical length of ISO Duration with 64 bit day
    // as the largest unit is 70 characters + 1 for a null terminator
    char *result = PyObject_Malloc(71);
    if (result == NULL) {
        PyErr_NoMemory();
        return NULL;
    }

    ret_code = make_iso_8601_timedelta(&tds, result, len);
    if (ret_code == -1) {
        PyErr_SetString(PyExc_ValueError,
                        "Could not convert timedelta value to string");
        PyObject_Free(result);
        return NULL;
    }

    return result;
}
