/*******************************************************************************
 * Copyright 2013-2021 Aerospike, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

#include <Python.h>
#include <stdbool.h>

#include <aerospike/as_error.h>
#include <aerospike/aerospike.h>
#include "client.h"
#include "conversions.h"
#include "exceptions.h"
#include "policy.h"

static PyObject *AerospikeClient_TruncateInvoke(AerospikeClient *self,
                                                char *namespace, char *set,
                                                uint64_t nanos,
                                                PyObject *py_policy,
                                                as_error *err)
{
    as_policy_info info_policy;
    as_policy_info *info_policy_p = NULL;
    as_status status = AEROSPIKE_OK;

    pyobject_to_policy_info(err, py_policy, &info_policy, &info_policy_p,
                            &self->as->config.policies.info);

    if (err->code != AEROSPIKE_OK) {
        as_error_update(err, AEROSPIKE_ERR_CLIENT, "Incorrect Policy");
        goto CLEANUP;
    }

    status =
        aerospike_truncate(self->as, err, info_policy_p, namespace, set, nanos);
    if (status != AEROSPIKE_OK) {
        return NULL;
    }

CLEANUP:

    if (err->code != AEROSPIKE_OK) {
        return NULL;
    }
    return PyLong_FromLong(0);
}

PyObject *AerospikeClient_Truncate(AerospikeClient *self, PyObject *args,
                                   PyObject *kwds)
{
    PyObject *py_set = NULL;
    PyObject *py_ns = NULL;
    PyObject *py_nanos = NULL;
    PyObject *py_policy = NULL;
    PyObject *ret_val = NULL;
    long long temp_long;
    as_error err;
    uint64_t nanos = 1; // If assignment fails, this will cause an error
    char *namespace = NULL;
    char *set = NULL;

    as_error_init(&err);

    static char *kwlist[] = {"namespace", "set", "nanos", "policy", NULL};

    if (PyArg_ParseTupleAndKeywords(args, kwds, "OOO|O:truncate", kwlist,
                                    &py_ns, &py_set, &py_nanos,
                                    &py_policy) == false) {
        return NULL;
    }

    if (!self || !self->as) {
        as_error_update(&err, AEROSPIKE_ERR_PARAM, "Invalid aerospike object");
        goto CLEANUP;
    }
    if (!self->is_conn_16) {
        as_error_update(&err, AEROSPIKE_ERR_CLUSTER,
                        "No connection to aerospike cluster");
        goto CLEANUP;
    }

    // Start conversion of the namespace parameter
    if (PyUnicode_Check(py_ns)) {
        namespace = strdup((char *)PyUnicode_AsUTF8(py_ns));
        // If we failed to copy the string, exit
        if (!namespace) {
            as_error_update(&err, AEROSPIKE_ERR_CLIENT,
                            "Memory allocation failed");
            goto CLEANUP;
        }
    }
    else {
        as_error_update(&err, AEROSPIKE_ERR_PARAM,
                        "Namespace must be unicode or string type");
        goto CLEANUP;
    }

    // Start conversion of the set parameter
    if (PyUnicode_Check(py_set)) {
        set = strdup((char *)PyUnicode_AsUTF8(py_set));
        // If we called strdup, and it failed we need to exit
        if (!set) {
            as_error_update(&err, AEROSPIKE_ERR_CLIENT,
                            "Memory allocation failed");
            goto CLEANUP;
        }
    }
    else if (py_set != Py_None) {
        // If the set is none, this is fine
        as_error_update(&err, AEROSPIKE_ERR_PARAM,
                        "Set must be None, or unicode or string type");
        goto CLEANUP;
    }

    // Start conversion of the nanosecond parameter
    if (PyLong_Check(py_nanos)) {

        temp_long = PyLong_AsLongLong(py_nanos);
        // There was a negative number outside of the range of - 2 ^ 63
        if (temp_long < 0 && !PyErr_Occurred()) {
            as_error_update(&err, AEROSPIKE_ERR_PARAM,
                            "Nanoseconds must be a positive value");
            goto CLEANUP;
        }
        // Its possible that this is a valid uint64 between 2 ^ 63 and 2^64 -1
        PyErr_Clear();
        nanos = (uint64_t)PyLong_AsUnsignedLongLong(py_nanos);

        if (PyErr_Occurred()) {
            as_error_update(&err, AEROSPIKE_ERR_PARAM,
                            "Nanoseconds value too large");
            goto CLEANUP;
        }
    }
    else {
        as_error_update(&err, AEROSPIKE_ERR_PARAM,
                        "Nanoseconds must be a long type");
        goto CLEANUP;
    }

    ret_val = AerospikeClient_TruncateInvoke(self, namespace, set, nanos,
                                             py_policy, &err);

CLEANUP:
    if (namespace) {
        free(namespace);
    }

    if (set) {
        free(set);
    }

    if (err.code != AEROSPIKE_OK) {
        raise_exception(&err);
        // If there was a returned value, we need to lower it's ref count here
        Py_XDECREF(ret_val);
        return NULL;
    }

    return ret_val;
}
