/*
 * -------------------------------------------------------------------------
 * This file is part of the MindStudio project.
 * Copyright (c) 2025 Huawei Technologies Co.,Ltd.
 *
 * MindStudio is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *
 *          http://license.coscl.org.cn/MulanPSL2
 *
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 * -------------------------------------------------------------------------
 */

#include <cstring>
#include <string>
#include <map>

#include "CPythonUtils.h"

namespace MindStudioDebugger {
namespace  CPythonUtils {

static std::map<std::string, PythonObject> PyObjMap = {};

int32_t RegisterPythonObject(const std::string& name, PythonObject obj)
{
    if (PyObjMap.find(name) != PyObjMap.end()) {
        return -1;
    }

    PyObjMap[name] = obj;
    return 0;
}

void UnRegisterPythonObject(const std::string& name)
{
    auto it = PyObjMap.find(name);
    if (it == PyObjMap.end()) {
        return;
    }

    PyObjMap.erase(it);
}

bool IsPyObjRegistered(const std::string& name)
{
    return PyObjMap.find(name) != PyObjMap.end();
}

PythonObject GetRegisteredPyObj(const std::string& name)
{
    auto it = PyObjMap.find(name);
    if (it == PyObjMap.end()) {
        return PythonObject();
    }
    return it->second;
}

PythonObject PythonObject::From(const PythonObject& input)
{
    return PythonObject(input);
}

PythonObject PythonObject::From(const int32_t& input)
{
    return PythonNumberObject::From(input);
}

PythonObject PythonObject::From(const uint32_t& input)
{
    return PythonNumberObject::From(input);
}

PythonObject PythonObject::From(const double& input)
{
    return PythonNumberObject::From(input);
}
PythonObject PythonObject::From(const std::string& input)
{
    return PythonStringObject::From(input);
}

PythonObject PythonObject::From(const char* input)
{
    return PythonStringObject::From(input);
}

PythonObject PythonObject::From(const bool& input)
{
    return PythonBoolObject::From(input);
}

int32_t PythonObject::To(int32_t& output) const
{
    if (!PyLong_Check(ptr)) {
        return -1;
    }
    output = static_cast<int32_t>(PyLong_AsLong(ptr));
    return 0;
}

int32_t PythonObject::To(uint32_t& output) const
{
    if (!PyLong_Check(ptr)) {
        return -1;
    }
    output = static_cast<uint32_t>(PyLong_AsUnsignedLong(ptr));
    return 0;
}

int32_t PythonObject::To(double& output) const
{
    if (!PyFloat_Check(ptr)) {
        return -1;
    }

    output = PyFloat_AsDouble(ptr);
    return 0;
}

int32_t PythonObject::To(std::string& output) const
{
    PyObject* strObj = PyObject_Str(ptr);
    if (strObj == nullptr) {
        return -1;
    }
    const char* s = PyUnicode_AsUTF8(strObj);
    if (s == nullptr) {
        Py_DECREF(strObj);
        return -1;
    }
    output = std::string(s);
    Py_DECREF(strObj);
    return 0;
}

int32_t PythonObject::To(bool& output) const
{
    output = static_cast<bool>(PyObject_IsTrue(ptr));
    return 0;
}

PythonObject PythonObject::Get(const std::string& name, bool ignore) const
{
    PyObject* o = PyObject_GetAttrString(ptr, name.c_str());
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }
    PythonObject ret(o);
    Py_XDECREF(o);
    return ret;
}

PythonObject PythonObject::Call(bool ignore) noexcept
{
    if (!PyCallable_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Object is not callable.");
        }
        return PythonObject();
    }

    PyObject* o = PyObject_CallObject(ptr, nullptr);
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }
    PythonObject ret(o);
    Py_XDECREF(o);
    return ret;
}

PythonObject PythonObject::Call(PythonTupleObject& args, bool ignore) noexcept
{
    if (!PyCallable_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Object is not callable.");
        }
        return PythonObject();
    }

    PyObject* o = PyObject_CallObject(ptr, args.IsNone() ? nullptr : reinterpret_cast<PyObject*>(&args));
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }
    PythonObject ret(o);
    Py_XDECREF(o);
    return ret;
}

PythonObject PythonObject::Call(PythonTupleObject& args, PythonDictObject& kwargs, bool ignore) noexcept
{
    if (!PyCallable_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Object is not callable.");
        }
        return PythonObject();
    }

    if (args.IsNone() || kwargs.IsNone()) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Call python object with invalid parameters.");
        }
        return PythonObject();
    }

    PyObject* o = PyObject_Call(ptr, args, kwargs);
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }
    PythonObject ret(o);
    Py_XDECREF(o);
    return ret;
}

PythonObject PythonObject::GetGlobal(const std::string& name, bool ignore)
{
    PyObject *globals = PyEval_GetGlobals();
    if (globals == nullptr) {
        if (ignore) {
            PyErr_Clear();
        }
        return PythonObject();
    }

    return PythonObject(PyDict_GetItemString(globals, name.c_str()));
}

PythonObject PythonObject::Import(const std::string& name, bool ignore) noexcept
{
    PyObject* m = PyImport_ImportModule(name.c_str());
    if (m == nullptr) {
        if (ignore) {
            PyErr_Clear();
        }
        return PythonObject();
    }
    PythonObject ret(m);
    Py_XDECREF(m);
    return ret;
}

PythonNumberObject::PythonNumberObject() : PythonObject()
{
    PyObject* o = PyLong_FromLong(0);
    SetPtr(o);
    Py_XDECREF(o);
}

PythonNumberObject::PythonNumberObject(PyObject* o) : PythonObject()
{
    if (!PyLong_Check(o) && !PyFloat_Check(o)) {
        return;
    }

    SetPtr(o);
}

PythonNumberObject PythonNumberObject::From(const int32_t& input)
{
    PythonNumberObject ret;
    PyObject* o = PyLong_FromLong(input);
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonNumberObject PythonNumberObject::From(const uint32_t& input)
{
    PythonNumberObject ret;
    PyObject* o = PyLong_FromUnsignedLong(input);
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonNumberObject PythonNumberObject::From(const double& input)
{
    PythonNumberObject ret;
    PyObject* o = PyFloat_FromDouble(input);
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonStringObject::PythonStringObject() : PythonObject()
{
    PyObject* o = PyUnicode_FromString("");
    SetPtr(o);
    Py_XDECREF(o);
}

PythonStringObject::PythonStringObject(PyObject* o) : PythonObject()
{
    if (!PyUnicode_Check(o)) {
        return;
    }

    SetPtr(o);
}

PythonStringObject PythonStringObject::From(const std::string& input)
{
    PythonStringObject ret;
    PyObject* o = PyUnicode_FromString(input.c_str());
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonStringObject PythonStringObject::From(const char* input)
{
    PythonStringObject ret;
    PyObject* o = PyUnicode_FromString(input);
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonBoolObject::PythonBoolObject() : PythonObject()
{
    SetPtr(Py_False);
}

PythonBoolObject::PythonBoolObject(PyObject* o) : PythonObject()
{
    if (!PyBool_Check(o)) {
        return;
    }

    SetPtr(o);
}

PythonBoolObject PythonBoolObject::From(const bool& input)
{
    PythonBoolObject ret;
    PyObject* o = PyBool_FromLong(input);
    if (o == nullptr) {
        return ret;
    }
    ret.SetPtr(o);
    Py_DECREF(o);
    return ret;
}

PythonListObject::PythonListObject() : PythonObject()
{
    PyObject* o = PyList_New(0);
    SetPtr(o);
    Py_XDECREF(o);
}

PythonListObject::PythonListObject(size_t size) : PythonObject()
{
    PyObject* o = PyList_New(size);
    SetPtr(o);
    Py_XDECREF(o);
}

PythonListObject::PythonListObject(PyObject* o) : PythonObject()
{
    if (!PyList_Check(o)) {
        return;
    }

    SetPtr(o);
}

size_t PythonListObject::Size() const
{
    if (!PyList_Check(ptr)) {
        return 0;
    }

    return PyList_GET_SIZE(ptr);
}

PythonObject PythonListObject::GetItem(size_t pos, bool ignore)
{
    if (!PyList_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Expect a list.");
        }
        return PythonObject();
    }
    if (static_cast<size_t>(PyList_GET_SIZE(ptr)) <= pos) {
        if (!ignore) {
            PyErr_SetString(PyExc_IndexError, "list index outof range");
        }
        return PythonObject();
    }

    PyObject* o = PyList_GetItem(ptr, pos);
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }

    return PythonObject(o);
}

PythonListObject& PythonListObject::SetItem(size_t pos, PythonObject& item, bool ignore)
{
    if (!PyList_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Expect a list.");
        }
        return *this;
    }

    if (static_cast<size_t>(PyList_GET_SIZE(ptr)) <= pos) {
        if (!ignore) {
            PyErr_SetString(PyExc_IndexError, "list index outof range");
        }
        return *this;
    }

    if (PyList_SetItem(ptr, pos, item.NewRef()) != 0) {
        if (ignore) {
            PyErr_Clear();
        }
    }
    return *this;
}

PythonListObject& PythonListObject::Insert(int64_t pos, PythonObject& item, bool ignore)
{
    if (!PyList_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Expect a list.");
        }
        return *this;
    }

    if (PyList_Insert(ptr, pos, item) != 0) {
        if (ignore) {
            PyErr_Clear();
        }
    }

    return *this;
}

PythonTupleObject PythonListObject::ToTuple(bool ignore)
{
    if (!PyList_Check(ptr)) {
        return PythonTupleObject();
    }

    PyObject* o = PyList_AsTuple(ptr);
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }
    PythonTupleObject ret(o);
    Py_XDECREF(o);
    return ret;
}

PythonTupleObject::PythonTupleObject() : PythonObject()
{
    PyObject* o = PyTuple_New(0);
    SetPtr(o);
    Py_XDECREF(o);
}

PythonTupleObject::PythonTupleObject(PyObject* o) : PythonObject()
{
    if (!o || !PyTuple_Check(o)) {
        return;
    }

    SetPtr(o);
}

size_t PythonTupleObject::Size() const
{
    if (!PyTuple_Check(ptr)) {
        return 0;
    }

    return PyTuple_GET_SIZE(ptr);
}

PythonObject PythonTupleObject::GetItem(size_t pos, bool ignore)
{
    if (!PyTuple_Check(ptr)) {
        if (!ignore) {
            PyErr_SetString(PyExc_TypeError, "Expect a tuple.");
        }
        return PythonObject();
    }
    if (static_cast<size_t>(PyTuple_GET_SIZE(ptr)) <= pos) {
        if (!ignore) {
            PyErr_SetString(PyExc_IndexError, "tuple index outof range");
        }
        return PythonObject();
    }

    PyObject* o = PyTuple_GetItem(ptr, pos);
    if (o == nullptr && ignore) {
        PyErr_Clear();
    }

    return PythonObject(o);
}

PythonDictObject::PythonDictObject() : PythonObject()
{
    PyObject* o = PyDict_New();
    SetPtr(o);
    Py_XDECREF(o);
}

PythonDictObject::PythonDictObject(PyObject* o) : PythonObject()
{
    if (!PyDict_Check(o)) {
        return;
    }

    SetPtr(o);
}

}
}