/*******************************************************************************
 * Copyright 2013-2017 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) {
		// The truncate operation failed. Update the err->code and return
		as_error_update(err, AEROSPIKE_ERR_CLIENT, "Truncate operation failed");
		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* py_ustr = 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;
	}

	// Start conversion of the namespace parameter
	if (PyString_Check(py_ns)) {
		namespace = strdup(PyString_AsString(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 if (PyUnicode_Check(py_ns)) {
		py_ustr = PyUnicode_AsUTF8String(py_ns);
		namespace = strdup(PyBytes_AsString(py_ustr));
		Py_DECREF(py_ustr);

		// 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 (PyString_Check(py_set)) {
		set = strdup(PyString_AsString(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 (PyUnicode_Check(py_set)) {
		py_ustr = PyUnicode_AsUTF8String(py_set);
		set = strdup(PyBytes_AsString(py_ustr));
		Py_DECREF(py_ustr);
		// 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 if PyInt_Check(py_nanos) {
		long tempInt;
		tempInt = PyInt_AsLong(py_nanos);

		if (tempInt == -1 && PyErr_Occurred()) {
			as_error_update(&err, AEROSPIKE_ERR_PARAM,"Nanoseconds value out of range for long");
			goto CLEANUP;
		}

		if (tempInt < 0) {
			as_error_update(&err, AEROSPIKE_ERR_PARAM, "Nanoseconds value must be a positive value");
			goto CLEANUP;
		}

		nanos = (uint64_t)tempInt;

	} 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) {
		PyObject * py_err = NULL;
		error_to_pyobject(&err, &py_err);
		PyObject *exception_type = raise_exception(&err);

		PyErr_SetObject(exception_type, py_err);
		Py_DECREF(py_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;
}
