//
// immer: immutable data structures for C++
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
//
// This software is distributed under the Boost Software License, Version 1.0.
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
//

extern "C" {
#include <Python.h>
#include <structmember.h>
}

#include <immer/vector.hpp>
#include <immer/algorithm.hpp>
#include <immer/refcount/unsafe_refcount_policy.hpp>

#include <iostream>

namespace {

struct heap_t
{
    template <typename ...Tags>
    static void* allocate(std::size_t size, Tags...)
    {
        return PyMem_Malloc(size);
    }

    template <typename ...Tags>
    static void deallocate(std::size_t, void* obj, Tags...)
    {
        PyMem_Free(obj);
    }
};

struct object_t
{
    struct wrap_t {};
    struct adopt_t {};

    PyObject* ptr_ = nullptr;

    object_t() = delete;
    ~object_t() { Py_XDECREF(ptr_); }

    explicit object_t(PyObject* p, wrap_t)  : ptr_{p} {}
    explicit object_t(PyObject* p, adopt_t) : ptr_{p}
    {
        assert(p);
        Py_INCREF(p);
    }

    static object_t wrap(PyObject* p)  { return object_t{p, wrap_t{}}; }
    static object_t adopt(PyObject* p) { return object_t{p, adopt_t{}}; }

    object_t(const object_t& o) : ptr_(o.ptr_) { Py_INCREF(ptr_); }
    object_t(object_t&& o) { std::swap(ptr_, o.ptr_); }

    object_t& operator=(const object_t& o)
    {
        Py_XINCREF(o.ptr_);
        Py_XDECREF(ptr_);
        ptr_ = o.ptr_;
        return *this;
    }
    object_t& operator=(object_t&& o)
    {
        std::swap(ptr_, o.ptr_);
        return *this;
    }

    PyObject* release()
    {
        auto p = ptr_;
        ptr_ = nullptr;
        return p;
    }

    PyObject* get() const { return ptr_; }
};

using memory_t = immer::memory_policy<
    immer::unsafe_free_list_heap_policy<heap_t>,
    immer::unsafe_refcount_policy>;

using vector_impl_t = immer::vector<object_t, memory_t>;

struct vector_t
{
    PyObject_HEAD
    vector_impl_t impl;
    PyObject*     in_weakreflist;

    static PyTypeObject type;
};

vector_t* empty_vector = nullptr;

vector_t* make_vector()
{
    auto* v = PyObject_GC_New(vector_t, &vector_t::type);
    new (&v->impl) vector_impl_t{};
    v->in_weakreflist = nullptr;
    PyObject_GC_Track((PyObject*)v);
    return v;
}

vector_t* make_vector(vector_impl_t&& impl)
{
    auto v = PyObject_GC_New(vector_t, &vector_t::type);
    new (&v->impl) vector_impl_t{std::move(impl)};
    v->in_weakreflist = nullptr;
    PyObject_GC_Track((PyObject*)v);
    return v;
}

auto todo()
{
    PyErr_SetString(PyExc_RuntimeError, "immer: todo!");
    return nullptr;
}

void vector_dealloc(vector_t* self)
{
    if (self->in_weakreflist != nullptr)
        PyObject_ClearWeakRefs((PyObject*)self);

    PyObject_GC_UnTrack((PyObject*)self);
    Py_TRASHCAN_SAFE_BEGIN(self);

    self->impl.~vector_impl_t();

    PyObject_GC_Del(self);
    Py_TRASHCAN_SAFE_END(self);
}

PyObject* vector_to_list(vector_t* self)
{
    auto list = PyList_New(self->impl.size());
    auto idx  = 0;
    immer::for_each(self->impl, [&] (auto&& obj) {
            auto o = obj.get();
            Py_INCREF(o);
            PyList_SET_ITEM(list, idx, o);
            ++idx;
        });
    return list;
}

PyObject* vector_repr(vector_t *self)
{
    auto list = vector_to_list(self);
    auto list_repr = PyObject_Repr(list);
    Py_DECREF(list);

    if (!list_repr) return nullptr;

#if PY_MAJOR_VERSION >= 3
    auto s = PyUnicode_FromFormat("%s%U%s", "immer.vector(", list_repr, ")");
    Py_DECREF(list_repr);
#else
    auto s = PyString_FromString("immer.vector(");
    PyString_ConcatAndDel(&s, list_repr);
    PyString_ConcatAndDel(&s, PyString_FromString(")"));
#endif
    return s;
}

Py_ssize_t vector_len(vector_t* self)
{
    return self->impl.size();
}

PyObject* vector_extend(vector_t* self, PyObject* iterable)
{
    return todo();
}

vector_t* vector_append(vector_t* self, PyObject *obj)
{
    assert(obj != nullptr);
    return make_vector(self->impl.push_back(object_t::adopt(obj)));
}

vector_t* vector_set(vector_t* self, PyObject* args)
{
    PyObject* obj = nullptr;
    Py_ssize_t pos;
    // the n parses for size, the O parses for a Python object
    if(!PyArg_ParseTuple(args, "nO", &pos, &obj)) {
        return nullptr;
    }
    if (pos < 0)
        pos += self->impl.size();
    if (pos < 0 || pos > (Py_ssize_t)self->impl.size()) {
        PyErr_Format(PyExc_IndexError, "Index out of range: %zi", pos);
        return nullptr;
    }
    return make_vector(self->impl.set(pos, object_t::adopt(obj)));
}

PyObject* vector_new(PyTypeObject* subtype, PyObject *args, PyObject *kwds)
{
    Py_INCREF(empty_vector);
    return (PyObject*)empty_vector;
}

long vector_hash(vector_t* self)
{
    todo();
    return 0;
}

PyObject* vector_get_item(vector_t* self, Py_ssize_t pos)
{
    if (pos < 0)
        pos += self->impl.size();
    if (pos < 0 || pos >= (Py_ssize_t)self->impl.size()) {
        PyErr_Format(PyExc_IndexError, "Index out of range: %zi", pos);
        return nullptr;
    }
    auto r = self->impl[pos];
    return r.release();
}

PyObject* vector_subscript(vector_t* self, PyObject* item)
{
    if (PyIndex_Check(item)) {
        auto i = PyNumber_AsSsize_t(item, PyExc_IndexError);
        if (i == -1 && PyErr_Occurred())
            return nullptr;
        return vector_get_item(self, i);
    } else if (PySlice_Check(item)) {
        return todo();
    } else {
        PyErr_Format(PyExc_TypeError,
                     "vector indices must be integers, not %.200s",
                     Py_TYPE(item)->tp_name);
        return nullptr;
    }
}

PyObject* vector_repeat(vector_t* self, Py_ssize_t n)
{
    return todo();
}

int vector_traverse(vector_t* self, visitproc visit, void* arg)
{
    auto result = 0;
    immer::all_of(self->impl, [&] (auto&& o) {
            return 0 == (result = [&] {
                Py_VISIT(o.get());
                return 0;
            }());
        });
    return result;
}

PyObject* vector_richcompare(PyObject* v, PyObject* w, int op)
{
    return todo();
}

PyObject* vector_iter(PyObject* self)
{
    return todo();
}

PyMethodDef vector_methods[] =
{
    {"append",      (PyCFunction)vector_append, METH_O, "Appends an element"},
    {"set",         (PyCFunction)vector_set, METH_VARARGS, "Inserts an element at the specified position"},
    {"extend",      (PyCFunction)vector_extend, METH_O|METH_COEXIST, "Extend"},
    {"tolist",      (PyCFunction)vector_to_list, METH_NOARGS, "Convert to list"},
    {0}
};

PyMemberDef vector_members[] =
{
    {0}  /* sentinel */
};

PySequenceMethods vector_sequence_methods =
{
    (lenfunc)vector_len,             /* sq_length */
    (binaryfunc)vector_extend,       /* sq_concat */
    (ssizeargfunc)vector_repeat,     /* sq_repeat */
    (ssizeargfunc)vector_get_item,   /* sq_item */
    0,                               /* sq_slice */
    0,                               /* sq_ass_item */
    0,                               /* sq_ass_slice */
    0,                               /* sq_contains */
    0,                               /* sq_inplace_concat */
    0,                               /* sq_inplace_repeat */
};

PyMappingMethods vector_mapping_methods =
{
    (lenfunc)vector_len,
    (binaryfunc)vector_subscript,
    0
};

PyTypeObject vector_t::type =
{
    PyVarObject_HEAD_INIT(NULL, 0)
    "immer.Vector",                             /* tp_name        */
    sizeof(vector_t),                           /* tp_basicsize   */
    0,		                                /* tp_itemsize    */
    (destructor)vector_dealloc,                 /* tp_dealloc     */
    0,                                          /* tp_print       */
    0,                                          /* tp_getattr     */
    0,                                          /* tp_setattr     */
    0,                                          /* tp_compare     */
    (reprfunc)vector_repr,                      /* tp_repr        */
    0,                                          /* tp_as_number   */
    &vector_sequence_methods,                   /* tp_as_sequence */
    &vector_mapping_methods,                    /* tp_as_mapping  */
    (hashfunc)vector_hash,                      /* tp_hash        */
    0,                                          /* tp_call        */
    0,                                          /* tp_str         */
    0,                                          /* tp_getattro    */
    0,                                          /* tp_setattro    */
    0,                                          /* tp_as_buffer   */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,    /* tp_flags       */
    "",                                         /* tp_doc         */
    (traverseproc)vector_traverse,              /* tp_traverse       */
    0,                                          /* tp_clear          */
    vector_richcompare,                         /* tp_richcompare    */
    offsetof(vector_t, in_weakreflist),         /* tp_weaklistoffset */
    vector_iter,                                /* tp_iter           */
    0,                                          /* tp_iternext       */
    vector_methods,                             /* tp_methods        */
    vector_members,                             /* tp_members        */
    0,                                          /* tp_getset         */
    0,                                          /* tp_base           */
    0,                                          /* tp_dict           */
    0,                                          /* tp_descr_get      */
    0,                                          /* tp_descr_set      */
    0,                                          /* tp_dictoffset     */
    0,                                          /* tp_init           */
    0,                                          /* tp_alloc */
    vector_new,                                 /* tp_new   */
};

#if PY_MAJOR_VERSION >= 3
struct PyModuleDef module_def =
{
    PyModuleDef_HEAD_INIT,
    "immer_python_module", /* m_name */
    "",                  /* m_doc */
    -1,                  /* m_size */
    /module_methods,     /* m_methods */
    0,                   /* m_reload */
    0,                   /* m_traverse */
    0,                   /* m_clear */
    0,                   /* m_free */
};
#endif

PyMethodDef module_methods[] = {
    {0, 0, 0, 0}
};

PyObject* module_init()
{
    if (PyType_Ready(&vector_t::type) < 0)
        return nullptr;

#if PY_MAJOR_VERSION >= 3
    auto m = PyModule_Create(&module_def);
#else
    auto m = Py_InitModule3("immer_python_module", module_methods, "");
#endif
    if (!m)
        return nullptr;

    if (!empty_vector)
        empty_vector = make_vector();

    Py_INCREF(&vector_t::type);
    PyModule_AddObject(m, "Vector", (PyObject*) &vector_t::type);
    return m;
}

} // anonymous namespace

extern "C" {
#if PY_MAJOR_VERSION >= 3
PyMODINIT_FUNC PyInit_immer_python_module()
{
    return module_init();
}
#else
PyMODINIT_FUNC initimmer_python_module()
{
    module_init();
}
#endif
}
