/*******************************************************************************
 * 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.
 ******************************************************************************/

#pragma once

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

#include <aerospike/as_admin.h>
#include <aerospike/as_key.h>
#include <aerospike/as_error.h>
#include <aerospike/as_record.h>
#include <aerospike/as_udf.h>
#include <aerospike/as_operations.h>
#include <aerospike/aerospike_batch.h>
#include <aerospike/as_exp.h>
#include <aerospike/as_partition_filter.h>
#include <aerospike/aerospike_stats.h>

#include "types.h"

#define CTX_KEY "ctx"

#define FIELD_NAME_BATCH_RECORDS "batch_records"
#define FIELD_NAME_BATCH_TYPE "_type"
#define FIELD_NAME_BATCH_HASWRITE "_has_write"
#define FIELD_NAME_BATCH_KEY "key"
#define FIELD_NAME_BATCH_OPS "ops"
#define FIELD_NAME_BATCH_META "meta"
#define FIELD_NAME_BATCH_RESULT "result"
#define FIELD_NAME_BATCH_RECORD "record"
#define FIELD_NAME_BATCH_POLICY "policy"
#define FIELD_NAME_BATCH_MODULE "module"
#define FIELD_NAME_BATCH_FUNCTION "function"
#define FIELD_NAME_BATCH_ARGS "args"
#define FIELD_NAME_BATCH_INDOUBT "in_doubt"

#define BATCH_TYPE_READ 0
#define BATCH_TYPE_WRITE 1
#define BATCH_TYPE_APPLY 2
#define BATCH_TYPE_REMOVE 3

#define PARTITIONS_STATUS_KEY_DONE "done"
#define PARTITIONS_STATUS_KEY_RETRY "retry"

as_status as_udf_file_to_pyobject(as_error *err, as_udf_file *entry,
                                  PyObject **py_file);

as_status as_udf_files_to_pyobject(as_error *err, as_udf_files *files,
                                   PyObject **py_files);

as_status str_array_of_roles_to_py_list(as_error *err, int num_elements,
                                        char str_array_ptr[][AS_ROLE_SIZE],
                                        PyObject *py_list);

as_status char_double_ptr_to_py_list(as_error *err, int num_elements,
                                     int element_size, char **str_array_ptr,
                                     PyObject *py_list);

as_status as_user_info_to_pyobject(as_error *err, as_user *user,
                                   PyObject **py_as_user);

as_status as_user_array_to_pyobject(as_error *err, as_user **users,
                                    PyObject **py_as_users, int users_size);

as_status as_user_info_array_to_pyobject(as_error *err, as_user **users,
                                         PyObject **py_as_users,
                                         int users_size);

as_status pyobject_to_strArray(as_error *err, PyObject *py_list, char **arr,
                               uint32_t max_len);

// On success, heap allocates a new as_val object and assigns its address to val
as_status as_val_new_from_pyobject(AerospikeClient *self, as_error *err,
                                   PyObject *py_obj, as_val **val,
                                   as_static_pool *static_pool,
                                   int serializer_type);

as_status pyobject_to_map(AerospikeClient *self, as_error *err,
                          PyObject *py_dict, as_map **map,
                          as_static_pool *static_pool, int serializer_type);

as_status pyobject_to_list(AerospikeClient *self, as_error *err,
                           PyObject *py_list, as_list **list,
                           as_static_pool *static_pool, int serializer_type);

as_status pyobject_to_key(as_error *err, PyObject *py_key, as_key *key);

as_status pyobject_to_index(AerospikeClient *self, as_error *err,
                            PyObject *py_value, long *long_val);

as_status as_record_init_from_pyobject(AerospikeClient *self, as_error *err,
                                       PyObject *py_rec, PyObject *py_meta,
                                       as_record *rec, int serializer_option,
                                       as_static_pool *static_pool);

as_status val_to_pyobject(AerospikeClient *self, as_error *err,
                          const as_val *val, PyObject **py_map);

as_status val_to_pyobject_cnvt_list_to_map(AerospikeClient *self, as_error *err,
                                           const as_val *val,
                                           PyObject **py_map);

as_status map_to_pyobject(AerospikeClient *self, as_error *err,
                          const as_map *map, PyObject **py_map);

as_status list_to_pyobject(AerospikeClient *self, as_error *err,
                           const as_list *list, PyObject **py_list);

as_status as_list_of_map_to_py_tuple_list(AerospikeClient *self, as_error *err,
                                          const as_list *list,
                                          PyObject **py_list);

as_status record_to_pyobject(AerospikeClient *self, as_error *err,
                             const as_record *rec, const as_key *key,
                             PyObject **obj);

as_status record_to_resultpyobject(AerospikeClient *self, as_error *err,
                                   const as_record *rec, PyObject **obj);

as_status operate_bins_to_pyobject(AerospikeClient *self, as_error *err,
                                   const as_record *rec, PyObject **py_bins);

as_status record_to_pyobject_cnvt_list_to_map(AerospikeClient *self,
                                              as_error *err,
                                              const as_record *rec,
                                              const as_key *key,
                                              PyObject **obj);

as_status key_to_pyobject(as_error *err, const as_key *key, PyObject **obj);

as_status metadata_to_pyobject(as_error *err, const as_record *rec,
                               PyObject **obj);

as_status bins_to_pyobject(AerospikeClient *self, as_error *err,
                           const as_record *rec, PyObject **obj,
                           bool cnvt_list_to_map);

void error_to_pyobject(const as_error *err, PyObject **obj);

as_status pyobject_to_astype_write(AerospikeClient *self, as_error *err,
                                   PyObject *py_value, as_val **val,
                                   as_static_pool *static_pool,
                                   int serializer_type);

as_status as_privilege_to_pyobject(as_error *err, as_privilege privileges[],
                                   PyObject *py_as_privilege,
                                   int privilege_size);

as_status as_role_to_pyobject_old(as_error *err, as_role *role,
                                  PyObject **py_as_role);

as_status as_role_to_pyobject(as_error *err, as_role *role,
                              PyObject *py_as_role);

as_status as_role_array_to_pyobject_old(as_error *err, as_role **roles,
                                        PyObject **py_as_roles, int roles_size);

as_status as_role_array_to_pyobject(as_error *err, as_role **roles,
                                    PyObject **py_as_roles, int roles_size);

as_status pyobject_to_as_privileges(as_error *err, PyObject *py_privileges,
                                    as_privilege **privileges,
                                    int privileges_size);

void initialize_bin_for_strictypes(AerospikeClient *self, as_error *err,
                                   PyObject *py_value, as_binop *binop,
                                   char *bin, as_static_pool *static_pool);

as_status bin_strict_type_checking(AerospikeClient *self, as_error *err,
                                   PyObject *py_bin, char **bin);

as_status check_and_set_meta(PyObject *py_meta, as_operations *ops,
                             as_error *err);

as_status as_batch_read_results_to_pyobject(as_error *err,
                                            AerospikeClient *client,
                                            const as_batch_read *results,
                                            uint32_t size,
                                            PyObject **py_records);

as_status batch_read_records_to_pyobject(AerospikeClient *self, as_error *err,
                                         as_batch_read_records *records,
                                         PyObject **py_recs);

as_status string_and_pyuni_from_pystring(PyObject *py_string,
                                         PyObject **pyuni_r, char **c_str_ptr,
                                         as_error *err);

as_status get_cdt_ctx(AerospikeClient *self, as_error *err, as_cdt_ctx *cdt_ctx,
                      PyObject *op_dict, bool *ctx_in_use,
                      as_static_pool *static_pool, int serializer_type);

// allow_base64_encoded_exprs: can the Python object also be a Python unicode object (base64 encoded)?
// if false, the Python object should only be a compiled Python expression object from aerospike_helpers
as_status as_exp_new_from_pyobject(AerospikeClient *self, PyObject *py_expr,
                                   as_exp **exp_list, as_error *err,
                                   bool allow_base64_encoded_exprs);

as_status convert_partition_filter(AerospikeClient *self,
                                   PyObject *py_partition_filter,
                                   as_partition_filter *partition_filter,
                                   as_partitions_status **ps, as_error *err);

as_status get_int_from_py_int(as_error *err, PyObject *py_long,
                              int *int_pointer, const char *py_object_name);

as_status
as_partitions_status_to_pyobject(as_error *err,
                                 const as_partitions_status *parts_status,
                                 PyObject **py_dict);

as_status as_partition_status_to_pyobject(
    as_error *err, const as_partition_status *part_status, PyObject **py_tuple);

as_status as_batch_result_to_BatchRecord(AerospikeClient *self, as_error *err,
                                         as_batch_result *bres,
                                         PyObject *py_batch_record,
                                         bool checking_if_records_exist);

PyObject *create_py_cluster_from_as_cluster(as_error *error_p,
                                            struct as_cluster_s *cluster);
PyObject *create_py_node_from_as_node(as_error *error_p,
                                      struct as_node_s *node);

// obj must be non-NULL
// Checks if pyobject is an instance of a class type defined in aerospike_helpers or one of its submodules
// If expected_submodule_name is NULL, the type is expected to be defined directly in the aerospike_helpers package
// If is_subclass_instance is true, we expect the instance's type to directly inherit from aerospike_helpers.<expected_submodule_name>.<expected_type_name>
// We need this method because classes defined natively in Python are heap-allocated types:
// https://docs.python.org/3/c-api/typeobj.html#c.PyTypeObject.tp_name
bool is_pyobj_correct_as_helpers_type(PyObject *obj,
                                      const char *expected_submodule_name,
                                      const char *expected_type_name,
                                      bool is_subclass_instance);
PyObject *create_class_instance_from_module(as_error *error_p,
                                            const char *module_name,
                                            const char *class_name,
                                            PyObject *py_arg);

// Convert a Python integer into a fixed-width integer and verify it is within that range
// We return an unsigned long long because it should be able to fit all fixed-width int types up to uint64_t
// Returns -1 on error. Error indicator can be checked to verify if error occurred
// TODO: replace this with new API calls in Python 3.14
unsigned long long
convert_pyobject_to_fixed_width_integer_type(PyObject *pyobject,
                                             unsigned long long max_bound);
uint8_t convert_pyobject_to_uint8_t(PyObject *pyobject);

uint16_t convert_pyobject_to_uint16_t(PyObject *pyobject);

uint32_t convert_pyobject_to_uint32_t(PyObject *pyobject);

uint64_t convert_pyobject_to_uint64_t(PyObject *pyobject);

// Returns NULL on error.
const char *convert_pyobject_to_str(PyObject *py_obj);

// Returns NULL on error.
PyObject *
create_py_cluster_stats_from_as_cluster_stats(as_error *err,
                                              as_cluster_stats *stats);
