/*
 * Copyright 2010-2017 The pygit2 contributors
 *
 * This file is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License, version 2,
 * as published by the Free Software Foundation.
 *
 * In addition to the permissions in the GNU General Public License,
 * the authors give you unlimited permission to link the compiled
 * version of this file into combinations with other programs,
 * and to distribute those combinations without any restriction
 * coming from the use of this file.  (The General Public License
 * restrictions do apply in other respects; for example, they cover
 * modification of the file, and distribution when not linked into
 * a combined executable.)
 *
 * This file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to
 * the Free Software Foundation, 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
 */

#ifndef INCLUDE_pygit2_utils_h
#define INCLUDE_pygit2_utils_h

#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <git2.h>
#include "types.h"

#ifdef __GNUC__
#  define PYGIT2_FN_UNUSED __attribute__((unused))
#else
#  define PYGIT2_FN_UNUSED
#endif

/* Python 2 support */
#ifndef Py_hash_t
  #define Py_hash_t long
#endif

#ifndef PyLong_AsSize_t
  #define PyLong_AsSize_t (size_t)PyLong_AsSsize_t
#endif

#if PY_MAJOR_VERSION == 2
  #define PyInt_AsSize_t (size_t)PyInt_AsLong
  #define PyInt_FromLongLong PyInt_FromLong
  #define PyBytes_AS_STRING PyString_AS_STRING
  #define PyBytes_AsString PyString_AsString
  #define PyBytes_AsStringAndSize PyString_AsStringAndSize
  #define PyBytes_Check PyString_Check
  #define PyBytes_FromString PyString_FromString
  #define PyBytes_FromStringAndSize PyString_FromStringAndSize
  #define PyBytes_Size PyString_Size
  #define to_path(x) to_bytes(x)
  #define to_encoding(x) to_bytes(x)
#else
  #define PyInt_Check PyLong_Check
  #define PyInt_FromSize_t PyLong_FromSize_t
  #define PyInt_FromLong PyLong_FromLong
  #define PyInt_FromLongLong PyLong_FromLongLong
  #define PyInt_AsLong PyLong_AsLong
  #define PyInt_AsSize_t PyLong_AsSize_t

  #define to_path(x) to_unicode(x, Py_FileSystemDefaultEncoding, "strict")
  #define to_encoding(x) PyUnicode_DecodeASCII(x, strlen(x), "strict")
  #define PyString_FromFormat(s, ...) PyUnicode_FromFormat(s, __VA_ARGS__)
#endif


#define CHECK_REFERENCE(self)\
    if (self->reference == NULL) {\
        PyErr_SetString(GitError, "deleted reference");\
        return NULL;\
    }

#define CHECK_REFERENCE_INT(self)\
    if (self->reference == NULL) {\
        PyErr_SetString(GitError, "deleted reference");\
        return -1;\
    }


/* Utilities */
#define to_unicode(x, encoding, errors)\
        to_unicode_n(x, strlen(x), encoding, errors)

PYGIT2_FN_UNUSED
Py_LOCAL_INLINE(PyObject*)
to_unicode_n(const char *value, size_t len, const char *encoding,
             const char *errors)
{
    if (encoding == NULL) {
        /* If the encoding is not explicit, it may not be UTF-8, so it
         * is not safe to decode it strictly.  This is rare in the
         * wild, but does occur in old commits to git itself
         * (e.g. c31820c2). */
        encoding = "utf-8";
        errors = "replace";
    }

    return PyUnicode_Decode(value, len, encoding, errors);
}

PYGIT2_FN_UNUSED
Py_LOCAL_INLINE(PyObject*)
to_bytes(const char * value)
{
    return PyBytes_FromString(value);
}

char * py_str_to_c_str(PyObject *value, const char *encoding);
const char *py_str_borrow_c_str(PyObject **tvaue, PyObject *value, const char *encoding);

PyObject * get_pylist_from_git_strarray(git_strarray *strarray);
int get_strarraygit_from_pylist(git_strarray *array, PyObject *pylist);

int py_object_to_otype(PyObject *py_type);

#define py_path_to_c_str(py_path) \
        py_str_to_c_str(py_path, Py_FileSystemDefaultEncoding)

/* Helpers to make shorter PyMethodDef and PyGetSetDef blocks */
#define METHOD(type, name, args)\
  {#name, (PyCFunction) type ## _ ## name, args, type ## _ ## name ## __doc__}

#define GETTER(type, attr)\
  {         #attr,\
   (getter) type ## _ ## attr ## __get__,\
            NULL,\
            type ## _ ## attr ## __doc__,\
            NULL}

#define GETSET(type, attr)\
  {         #attr,\
   (getter) type ## _ ## attr ## __get__,\
   (setter) type ## _ ## attr ## __set__,\
            type ## _ ## attr ## __doc__,\
            NULL}

#define MEMBER(type, attr, attr_type, docstr)\
  {#attr, attr_type, offsetof(type, attr), 0, PyDoc_STR(docstr)}

#define RMEMBER(type, attr, attr_type, docstr)\
  {#attr, attr_type, offsetof(type, attr), READONLY, PyDoc_STR(docstr)}


/* Helpers for memory allocation */
#define CALLOC(ptr, num, size, label) \
        ptr = calloc((num), size);\
        if (ptr == NULL) {\
            err = GIT_ERROR;\
            giterr_set_oom();\
            goto label;\
        }

#define MALLOC(ptr, size, label) \
        ptr = malloc(size);\
        if (ptr == NULL) {\
            err = GIT_ERROR;\
            giterr_set_oom();\
            goto label;\
        }

/* Helpers to make type init shorter. */
#define INIT_TYPE(type, base, new) \
    type.tp_base = base; \
    type.tp_new = new; \
    if (PyType_Ready(&type) < 0) return NULL;

#define ADD_TYPE(module, type) \
    Py_INCREF(& type ## Type);\
    if (PyModule_AddObject(module, #type, (PyObject*) & type ## Type) == -1)\
        return NULL;

#define ADD_CONSTANT_INT(m, name) \
    if (PyModule_AddIntConstant(m, #name, name) == -1) return NULL;

#define ADD_CONSTANT_STR(m, name) \
    if (PyModule_AddStringConstant(m, #name, name) == -1) return NULL;


#endif