/*
 * Copyright (C) 2012-2013 Red Hat, Inc.
 *
 * Licensed under the GNU Lesser General Public License Version 2.1
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include <Python.h>
#include <glib.h>
#include <structmember.h>
#include <stddef.h>
#include <solv/util.h>

#include "dnf-types.h"
#include "dnf-goal.h"

#include "exception-py.hpp"
#include "goal-py.hpp"
#include "hy-goal-private.hpp"
#include "iutil-py.hpp"
#include "package-py.hpp"
#include "selector-py.hpp"
#include "sack-py.hpp"
#include "query-py.hpp"
#include "pycomp.hpp"
#include "goal/Goal.hpp"
#include "sack/query.hpp"
#include "sack/packageset.hpp"

#define BLOCK_SIZE 15

typedef struct {
    PyObject_HEAD
    HyGoal goal;
    PyObject *sack;
} _GoalObject;

static int
args_pkg_sltr_check(DnfPackage *pkg, HySelector sltr)
{
    if (!(pkg || sltr)) {
        PyErr_SetString(PyExc_ValueError,
                        "Requires a Package or a Selector argument.");
        return 0;
    }
    if (pkg && sltr) {
        PyErr_SetString(PyExc_ValueError,
                        "Does not accept both Package and Selector arguments.");
        return 0;
    }
    return 1;
}

static int
args_pkg_sltr_parse(PyObject *args, PyObject *kwds,
                     DnfPackage **pkg, HySelector *sltr, int *flags, int flag_mask)
{
    const char *kwlist[] = {"package", "select", "clean_deps", "check_installed",
                      "optional", NULL};
    int clean_deps = 0, check_installed = 0, optional = 0;
    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|O&O&iii", (char**) kwlist,
                                     package_converter, pkg,
                                     selector_converter, sltr,
                                     &clean_deps, &check_installed,
                                     &optional))
        return 0;
    if (!args_pkg_sltr_check(*pkg, *sltr))
        return 0;
    if (clean_deps) {
        if (!(flag_mask & HY_CLEAN_DEPS)) {
            PyErr_SetString(PyExc_ValueError,
                            "Does not accept clean_deps keyword") ;
            return 0;
        }
        *flags |= HY_CLEAN_DEPS;
    }
    if (check_installed) {
        if  (!(flag_mask & HY_CHECK_INSTALLED)) {
            PyErr_SetString(PyExc_ValueError,
                            "Does not accept check_installed keyword") ;
            return 0;
        }
        *flags |= HY_CHECK_INSTALLED;
    }
    if (optional) {
        if  (!(flag_mask & HY_WEAK_SOLV)) {
            PyErr_SetString(PyExc_ValueError,
                            "Does not accept optional keyword");
            return 0;
        }
        *flags |= HY_WEAK_SOLV;
    }
    return 1;
}

static int
args_run_parse(PyObject *args, PyObject *kwds, int *flags, PyObject **callback_p)
{
    const char *kwlist[] = {"callback", "allow_uninstall", "force_best", "verify",
        "ignore_weak_deps", "ignore_weak", NULL};
    int ignore_weak = 0;
    int ignore_weak_deps = 0;
    int allow_uninstall = 0;
    int force_best = 0;
    int verify = 0;
    PyObject *callback = NULL;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|Oiiiii", (char**) kwlist,
                                     &callback, &allow_uninstall, &force_best,
                                     &verify, &ignore_weak_deps, &ignore_weak))
        return 0;

    if (callback) {
        if (!callback_p) {
            PyErr_SetString(PyExc_ValueError,
                            "Does not accept a callback argument.");
            return 0;
        }
        if (!PyCallable_Check(callback)) {
            PyErr_SetString(PyExc_ValueError, "Must be a callable object.");
            return 0;
        }
        *callback_p = callback;
    } else if (callback_p) {
        PyErr_SetString(PyExc_ValueError, "Expected a callback argument.");
        return 0;
    }

    if (allow_uninstall)
        *flags |= DNF_ALLOW_UNINSTALL;
    if (force_best)
        *flags |= DNF_FORCE_BEST;
    if (verify)
        *flags |= DNF_VERIFY;
    if (ignore_weak_deps)
        *flags |= DNF_IGNORE_WEAK_DEPS;
    if (ignore_weak)
        *flags |= DNF_IGNORE_WEAK;
    return 1;
}

static PyObject *
op_ret2exc(int ret)
{
    if (!ret)
        Py_RETURN_NONE;

    switch (ret) {
    case DNF_ERROR_BAD_SELECTOR:
        PyErr_SetString(HyExc_Value,
                        "Ill-formed Selector used for the operation.");
        return NULL;
    case DNF_ERROR_INVALID_ARCHITECTURE:
        PyErr_SetString(HyExc_Arch, "Used arch is unknown.");
        return NULL;
    case DNF_ERROR_PACKAGE_NOT_FOUND:
        PyErr_SetString(HyExc_Validation, "The validation check has failed.");
        return NULL;
    default:
        PyErr_SetString(HyExc_Exception, "Goal operation failed.");
        return NULL;
    }
}

/* functions on the type */

static PyObject *
goal_new(PyTypeObject *type, PyObject *args, PyObject *kwds) try
{
    _GoalObject *self = (_GoalObject*)type->tp_alloc(type, 0);
    if (self) {
        self->goal = NULL;
        self->sack = NULL;
    }
    return (PyObject*)self;
} CATCH_TO_PYTHON

static void
goal_dealloc(_GoalObject *self)
{
    if (self->goal)
        hy_goal_free(self->goal);

    Py_XDECREF(self->sack);
    Py_TYPE(self)->tp_free(self);
}

static int
goal_init(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    PyObject *sack;
    DnfSack *csack;

    if (!PyArg_ParseTuple(args, "O!", &sack_Type, &sack))
        return -1;
    csack = sackFromPyObject(sack);
    if (csack == NULL)
        return -1;
    self->sack = sack;
    Py_INCREF(self->sack); // sack has to kept around until we are
    self->goal = hy_goal_create(csack);
    return 0;
} CATCH_TO_PYTHON_INT

/* object methods */

static PyObject *
distupgrade_all(_GoalObject *self, PyObject *unused) try
{
    int ret = hy_goal_distupgrade_all(self->goal);
    return op_ret2exc(ret);
} CATCH_TO_PYTHON

static PyObject *
distupgrade(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    DnfPackage *pkg = NULL;
    HySelector sltr = NULL;
    if (!args_pkg_sltr_parse(args, kwds, &pkg, &sltr, NULL, 0))
        return NULL;

    int ret = pkg ? hy_goal_distupgrade(self->goal, pkg) :
        hy_goal_distupgrade_selector(self->goal, sltr);
    return op_ret2exc(ret);
} CATCH_TO_PYTHON
 
static PyObject *
get_protect_running_kernel(_GoalObject *self, void * unused) try
{
    return PyBool_FromLong(self->goal->get_protect_running_kernel());
} CATCH_TO_PYTHON

static int
set_protect_running_kernel(_GoalObject *self, PyObject * value, void * closure) try
{
    if(!PyBool_Check(value)) {
        PyErr_SetString(PyExc_TypeError, "Only Bool Type accepted");
        return -1;
    }
    int c_value = PyObject_IsTrue(value);
    self->goal->set_protect_running_kernel(c_value);
    return 0;
} CATCH_TO_PYTHON_INT

static PyObject *
erase(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    DnfPackage *pkg = NULL;
    HySelector sltr = NULL;
    int flags = 0;
    if (!args_pkg_sltr_parse(args, kwds, &pkg, &sltr, &flags, HY_CLEAN_DEPS))
        return NULL;

    int ret = pkg ? hy_goal_erase_flags(self->goal, pkg, flags) :
        hy_goal_erase_selector_flags(self->goal, sltr, flags);
    return op_ret2exc(ret);
} CATCH_TO_PYTHON

static PyObject *
install(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    DnfPackage *pkg = NULL;
    HySelector sltr = NULL;
    int flags = 0;
    g_autoptr(GError) error = NULL;
    if (!args_pkg_sltr_parse(args, kwds, &pkg, &sltr, &flags, HY_WEAK_SOLV))
        return NULL;

    if (flags & HY_WEAK_SOLV) {
        if (pkg) {
            hy_goal_install_optional(self->goal, pkg);
        } else {
            hy_goal_install_selector_optional(self->goal, sltr, &error);
        }
    } else {
        if (pkg) {
            hy_goal_install(self->goal, pkg);
        } else {
            hy_goal_install_selector(self->goal, sltr, &error);
        }
    }
    return op_error2exc(error);
} CATCH_TO_PYTHON

static PyObject *
upgrade(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    DnfPackage *pkg = NULL;
    HySelector sltr = NULL;

    if (!args_pkg_sltr_parse(args, kwds, &pkg, &sltr, NULL, 0))
        return NULL;
    if (pkg) {
        int ret = hy_goal_upgrade_to(self->goal, pkg);
        return op_ret2exc(ret);
    }
    int ret = hy_goal_upgrade_selector(self->goal, sltr);
    return op_ret2exc(ret);
} CATCH_TO_PYTHON

static PyObject *
upgrade_all(_GoalObject *self, PyObject *unused) try
{
    int ret = hy_goal_upgrade_all(self->goal);
    return op_ret2exc(ret);
} CATCH_TO_PYTHON

static PyObject *
userinstalled(_GoalObject *self, PyObject *obj) try
{
    if (queryObject_Check(obj)) {
        HyQuery query = queryFromPyObject(obj);
        if (query == NULL)
            return NULL;
        self->goal->userInstalled(*query->getResultPset());
        Py_RETURN_FALSE;
    }
    DnfPackage *cpkg = packageFromPyObject(obj);
    int ret;

    if (cpkg == NULL)
        return NULL;
    ret = hy_goal_userinstalled(self->goal, cpkg);
    if (!ret)
        Py_RETURN_TRUE;
    Py_RETURN_FALSE;
} CATCH_TO_PYTHON

static PyObject *
get_actions(_GoalObject *self, void *unused) try
{
    HyGoal goal = self->goal;
    return PyLong_FromLong(goal->getActions());
} CATCH_TO_PYTHON

static PyObject *
req_has_distupgrade_all(_GoalObject *self, PyObject *unused) try
{
    return PyBool_FromLong(hy_goal_has_actions(self->goal, DNF_DISTUPGRADE_ALL));
} CATCH_TO_PYTHON

static PyObject *
req_has_erase(_GoalObject *self, PyObject *unused) try
{
    return PyBool_FromLong(hy_goal_has_actions(self->goal, DNF_ERASE));
} CATCH_TO_PYTHON

static PyObject *
req_length(_GoalObject *self, PyObject *unused) try
{
    return PyLong_FromLong(hy_goal_req_length(self->goal));
} CATCH_TO_PYTHON

static PyObject *
add_protected(_GoalObject *self, PyObject *seq) try
{
    HyGoal goal = self->goal;
    auto pset = pyseq_to_packageset(seq, hy_goal_get_sack(goal));
    if (!pset)
        return NULL;
    dnf_goal_add_protected(goal, pset.get());
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
add_exclude_from_weak(_GoalObject *self, PyObject *seq) try
{
    HyGoal goal = self->goal;
    auto pset = pyseq_to_packageset(seq, hy_goal_get_sack(goal));
    if (!pset)
        return NULL;
    goal->add_exclude_from_weak(*(pset.get()));
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
reset_exclude_from_weak(_GoalObject *self, PyObject *unused) try
{
    HyGoal goal = self->goal;
    goal->reset_exclude_from_weak();
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
exclude_from_weak_autodetect(_GoalObject *self, PyObject *unused) try
{
    HyGoal goal = self->goal;
    goal->exclude_from_weak_autodetect();
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
run(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    int flags = 0;
    if (!args_run_parse(args, kwds, &flags, NULL))
        return NULL;

    int ret = hy_goal_run_flags(self->goal, static_cast<DnfGoalActions>(flags));
    if (!ret)
        Py_RETURN_TRUE;
    Py_RETURN_FALSE;
} CATCH_TO_PYTHON

static PyObject *
count_problems(_GoalObject *self, PyObject *unused) try
{
    return PyLong_FromLong(hy_goal_count_problems(self->goal));
} CATCH_TO_PYTHON

/**
 * Reports problems described in strings.
 *
 * Returns Python list with Python list of strings for each problem or empty Python list if no
 * problem or NULL in case of error.
 */
static PyObject *
problem_rules(_GoalObject *self, PyObject *unused) try
{
    auto allProblems = self->goal->describeAllProblemRules(true);
    return problemRulesPyConverter(allProblems);
} CATCH_TO_PYTHON

/**
 * Reports packages that has a conflict
 *
 * Returns Python list with package objects that have a conflict.
 */
static PyObject *
problem_conflicts(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    const char *kwlist[] = {"available", NULL};
    int available = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", (char**) kwlist, &available))
        return 0;

    DnfPackageState pkg_type = DNF_PACKAGE_STATE_ALL;
    if (available)
        pkg_type = DNF_PACKAGE_STATE_AVAILABLE;
    auto pset = self->goal->listConflictPkgs(pkg_type);
    return packageset_to_pylist(pset.get(), self->sack);
} CATCH_TO_PYTHON

/**
 * Reports packages that has a conflict
 *
 * Returns Python list with package objects that have a conflict.
 */
static PyObject *
problem_broken_dependency(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    const char *kwlist[] = {"available", NULL};
    int available = 0;

    if (!PyArg_ParseTupleAndKeywords(args, kwds, "|i", (char**) kwlist, &available))
        return 0;

    DnfPackageState pkg_type = DNF_PACKAGE_STATE_ALL;

    if (available)
        pkg_type = DNF_PACKAGE_STATE_AVAILABLE;
    auto pset = self->goal->listBrokenDependencyPkgs(pkg_type);

    return packageset_to_pylist(pset.get(), self->sack);
} CATCH_TO_PYTHON

static PyObject *
log_decisions(_GoalObject *self, PyObject *unused) try
{
    if (hy_goal_log_decisions(self->goal))
        PyErr_SetString(PyExc_ValueError, "log_decisions() failed.");
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
write_debugdata(_GoalObject *self, PyObject *dir_str) try
{
    g_autoptr(GError) error = NULL;
    PycompString dir(dir_str);

    if (!dir.getCString())
        return NULL;

    gboolean ret = hy_goal_write_debugdata(self->goal, dir.getCString(), &error);
    if (!ret) {
        op_error2exc(error);
        return NULL;
    }
    Py_RETURN_NONE;
} CATCH_TO_PYTHON

static PyObject *
list_generic(_GoalObject *self, GPtrArray *(*func)(HyGoal, GError **))
{
    g_autoptr(GError) error = NULL;
    GPtrArray *plist = func(self->goal, &error);
    PyObject *list;

    if (!plist) {
        switch (error->code) {
        case DNF_ERROR_INTERNAL_ERROR:
            PyErr_SetString(HyExc_Value, "Goal has not been run yet.");
            break;
        case DNF_ERROR_NO_SOLUTION:
            PyErr_SetString(HyExc_Runtime, "Goal could not find a solution.");
            break;
        default:
            assert(0);
        }
        return NULL;
    }
    list = packagelist_to_pylist(plist, self->sack);
    g_ptr_array_unref(plist);
    return list;
}

static PyObject *
list_erasures(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_erasures);
} CATCH_TO_PYTHON

static PyObject *
list_installs(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_installs);
} CATCH_TO_PYTHON

static PyObject *
list_obsoleted(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_obsoleted);
} CATCH_TO_PYTHON

static PyObject *
list_reinstalls(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_reinstalls);
} CATCH_TO_PYTHON

static PyObject *
list_unneeded(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_unneeded);
} CATCH_TO_PYTHON

static PyObject *
list_suggested(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_suggested);
} CATCH_TO_PYTHON

static PyObject *
list_downgrades(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_downgrades);
} CATCH_TO_PYTHON

static PyObject *
list_upgrades(_GoalObject *self, PyObject *unused) try
{
    return list_generic(self, hy_goal_list_upgrades);
} CATCH_TO_PYTHON

static PyObject *
obsoleted_by_package(_GoalObject *self, PyObject *pkg) try
{
    DnfPackage *cpkg = packageFromPyObject(pkg);

    if (cpkg == NULL)
        return NULL;
    GPtrArray *plist = hy_goal_list_obsoleted_by_package(self->goal, cpkg);
    PyObject *list = packagelist_to_pylist(plist, self->sack);
    g_ptr_array_unref(plist);
    return list;
} CATCH_TO_PYTHON

static PyObject *
get_reason(_GoalObject *self, PyObject *pkg) try
{
    DnfPackage *cpkg = packageFromPyObject(pkg);

    if (cpkg == NULL)
        return NULL;
    int reason = hy_goal_get_reason(self->goal, cpkg);
    return PyLong_FromLong(reason);
} CATCH_TO_PYTHON

static PyObject *
goalToPyObject(HyGoal goal, PyObject *sack)
{
    _GoalObject *self = (_GoalObject *)goal_Type.tp_alloc(&goal_Type, 0);
    if (self) {
        self->goal = goal;
        self->sack = sack;
        Py_INCREF(sack);
    }
    return (PyObject *)self;
}

static PyObject *
deepcopy(_GoalObject *self, PyObject *args, PyObject *kwds) try
{
    HyGoal goal = hy_goal_clone(self->goal);
    return goalToPyObject(goal, self->sack);
} CATCH_TO_PYTHON

static struct PyMethodDef goal_methods[] = {
    {"__deepcopy__", (PyCFunction)deepcopy, METH_KEYWORDS|METH_VARARGS,
     NULL},
    {"add_protected", (PyCFunction)add_protected, METH_O,
     NULL},
    {"add_exclude_from_weak", (PyCFunction)add_exclude_from_weak, METH_O, NULL},
    {"reset_exclude_from_weak", (PyCFunction)reset_exclude_from_weak, METH_NOARGS, NULL},
    {"exclude_from_weak_autodetect", (PyCFunction)exclude_from_weak_autodetect, METH_NOARGS, NULL},
    {"distupgrade_all",        (PyCFunction)distupgrade_all,        METH_NOARGS,        NULL},
    {"distupgrade",                (PyCFunction)distupgrade,
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"erase",                (PyCFunction)erase,
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"install",                (PyCFunction)install,
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"upgrade",                (PyCFunction)upgrade,
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"upgrade_all",        (PyCFunction)upgrade_all,        METH_NOARGS,        NULL},
    {"userinstalled",        (PyCFunction)userinstalled,        METH_O,                NULL},
    // deprecated in 0.5.9, will be removed in 1.0.0
    // use goal.actions & hawkey.DISTUPGRADE_ALL instead
    {"req_has_distupgrade_all",        (PyCFunction)req_has_distupgrade_all,
     METH_NOARGS,        NULL},
    // deprecated in 0.5.9, will be removed in 1.0.0
    // use goal.actions | hawkey.ERASE instead
    {"req_has_erase",        (PyCFunction)req_has_erase,        METH_NOARGS,        NULL},
    {"req_length",        (PyCFunction)req_length,        METH_NOARGS,        NULL},
    {"run",                (PyCFunction)run,
     METH_VARARGS | METH_KEYWORDS, NULL},
    {"count_problems",        (PyCFunction)count_problems,        METH_NOARGS,        NULL},
    {"problem_conflicts",(PyCFunction)problem_conflicts,        METH_VARARGS | METH_KEYWORDS,                NULL},
    {"problem_broken_dependency",(PyCFunction)problem_broken_dependency,        METH_VARARGS | METH_KEYWORDS,                NULL},
    {"problem_rules", (PyCFunction)problem_rules,        METH_NOARGS,                NULL},
    {"log_decisions",   (PyCFunction)log_decisions,        METH_NOARGS,        NULL},
    {"write_debugdata", (PyCFunction)write_debugdata,        METH_O,                NULL},
    {"list_erasures",        (PyCFunction)list_erasures,        METH_NOARGS,        NULL},
    {"list_installs",        (PyCFunction)list_installs,        METH_NOARGS,        NULL},
    {"list_obsoleted",        (PyCFunction)list_obsoleted,        METH_NOARGS,        NULL},
    {"list_reinstalls",        (PyCFunction)list_reinstalls,        METH_NOARGS,        NULL},
    {"list_unneeded",        (PyCFunction)list_unneeded,        METH_NOARGS,        NULL},
    {"list_suggested",       (PyCFunction)list_suggested,       METH_NOARGS,        NULL},
    {"list_downgrades",        (PyCFunction)list_downgrades,        METH_NOARGS,        NULL},
    {"list_upgrades",        (PyCFunction)list_upgrades,        METH_NOARGS,        NULL},
    {"obsoleted_by_package",(PyCFunction)obsoleted_by_package,
     METH_O, NULL},
    {"get_reason",        (PyCFunction)get_reason,        METH_O,                NULL},
    {NULL}                      /* sentinel */
};

static struct PyMemberDef goal_members[] = {
    {(char*)"sack", T_OBJECT, offsetof(_GoalObject, sack), READONLY, NULL},
    {NULL}
};

static PyGetSetDef goal_getsetters[] = {
    {(char*)"actions",        (getter)get_actions, NULL, NULL, NULL},
    {(char*)"protect_running_kernel", (getter)get_protect_running_kernel,
        (setter)set_protect_running_kernel, NULL, NULL},
    {NULL}                /* sentinel */
};

PyTypeObject goal_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_hawkey.Goal",                /*tp_name*/
    sizeof(_GoalObject),        /*tp_basicsize*/
    0,                                /*tp_itemsize*/
    (destructor) goal_dealloc,  /*tp_dealloc*/
    0,                                /*tp_print*/
    0,                                /*tp_getattr*/
    0,                                /*tp_setattr*/
    0,                                /*tp_compare*/
    0,                                /*tp_repr*/
    0,                                /*tp_as_number*/
    0,                                /*tp_as_sequence*/
    0,                                /*tp_as_mapping*/
    0,                                /*tp_hash */
    0,                                /*tp_call*/
    0,                                /*tp_str*/
    PyObject_GenericGetAttr,        /*tp_getattro*/
    0,                                /*tp_setattro*/
    0,                                /*tp_as_buffer*/
    Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,        /*tp_flags*/
    "Goal object",                /* tp_doc */
    0,                                /* tp_traverse */
    0,                                /* tp_clear */
    0,                                /* tp_richcompare */
    0,                                /* tp_weaklistoffset */
    PyObject_SelfIter,                /* tp_iter */
    0,                                 /* tp_iternext */
    goal_methods,                /* tp_methods */
    goal_members,                /* tp_members */
    goal_getsetters,                                /* tp_getset */
    0,                                /* tp_base */
    0,                                /* tp_dict */
    0,                                /* tp_descr_get */
    0,                                /* tp_descr_set */
    0,                                /* tp_dictoffset */
    (initproc)goal_init,        /* tp_init */
    0,                                /* tp_alloc */
    goal_new,                        /* tp_new */
    0,                                /* tp_free */
    0,                                /* tp_is_gc */
};
