// Copyright (C) 2012  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

// Enable this if you use s# variants with PyArg_ParseTuple(), see
// http://docs.python.org/py3k/c-api/arg.html#strings-and-buffers
//#define PY_SSIZE_T_CLEAN

// Python.h needs to be placed at the head of the program file, see:
// http://docs.python.org/py3k/extending/extending.html#a-simple-example
#include <Python.h>

#include <string>
#include <stdexcept>

#include <util/python/pycppwrapper_util.h>

#include <dns/python/rrclass_python.h>
#include <dns/python/name_python.h>
#include <dns/python/pydnspp_common.h>

#include <datasrc/client_list.h>

#include "configurableclientlist_python.h"
#include "datasrc.h"
#include "finder_python.h"
#include "client_python.h"
#include "zonewriter_python.h"

using namespace std;
using namespace isc::util::python;
using namespace isc::datasrc;
using namespace isc::datasrc::memory;
using namespace isc::datasrc::python;
using namespace isc::datasrc::memory::python;
using namespace isc::dns::python;

//
// ConfigurableClientList
//

// Trivial constructor.
s_ConfigurableClientList::s_ConfigurableClientList() : cppobj(NULL) {
}

namespace {

int
ConfigurableClientList_init(PyObject* po_self, PyObject* args, PyObject*) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    try {
        const PyObject* rrclass;
        if (PyArg_ParseTuple(args, "O!", &isc::dns::python::rrclass_type,
                             &rrclass)) {
            self->cppobj =
                new ConfigurableClientList(isc::dns::python::
                                           PyRRClass_ToRRClass(rrclass));
            return (0);
        }
    } catch (const exception& ex) {
        const string ex_what =
            "Failed to construct ConfigurableClientList object: " +
            string(ex.what());
        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
        return (-1);
    } catch (...) {
        PyErr_SetString(PyExc_SystemError, "Unexpected C++ exception");
        return (-1);
    }

    return (-1);
}

void
ConfigurableClientList_destroy(PyObject* po_self) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    delete self->cppobj;
    self->cppobj = NULL;
    Py_TYPE(self)->tp_free(self);
}

PyObject*
ConfigurableClientList_configure(PyObject* po_self, PyObject* args) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    try {
        const char* configuration;
        int allow_cache;
        if (PyArg_ParseTuple(args, "si", &configuration, &allow_cache)) {
            const isc::data::ConstElementPtr
                element(isc::data::Element::fromJSON(string(configuration)));
            self->cppobj->configure(element, allow_cache);
            Py_RETURN_NONE;
        } else {
            return (NULL);
        }
    } catch (const isc::data::JSONError& jse) {
        const string ex_what(std::string("JSON parse error in data source"
                               " configuration: ") + jse.what());
        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
        return (NULL);
    } catch (const std::exception& exc) {
        PyErr_SetString(getDataSourceException("Error"), exc.what());
        return (NULL);
    } catch (...) {
        PyErr_SetString(getDataSourceException("Error"),
                        "Unknown C++ exception");
        return (NULL);
    }
}

PyObject*
ConfigurableClientList_resetMemorySegment(PyObject* po_self, PyObject* args) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    try {
        const char* datasrc_name_p;
        int mode_int;
        const char* config_p;
        if (PyArg_ParseTuple(args, "sis", &datasrc_name_p, &mode_int,
                             &config_p)) {
            const std::string datasrc_name(datasrc_name_p);
            const isc::data::ConstElementPtr
                config(isc::data::Element::fromJSON(std::string(config_p)));
            ZoneTableSegment::MemorySegmentOpenMode mode =
                static_cast<ZoneTableSegment::MemorySegmentOpenMode>
                    (mode_int);
            self->cppobj->resetMemorySegment(datasrc_name, mode, config);
            Py_RETURN_NONE;
        }
    } catch (const isc::data::JSONError& jse) {
        const string ex_what(std::string("JSON parse error in memory segment"
                               " configuration: ") + jse.what());
        PyErr_SetString(getDataSourceException("Error"), ex_what.c_str());
    } catch (const std::exception& exc) {
        PyErr_SetString(getDataSourceException("Error"), exc.what());
    } catch (...) {
        PyErr_SetString(getDataSourceException("Error"),
                        "Unknown C++ exception");
    }

    return (NULL);
}

PyObject*
ConfigurableClientList_getCachedZoneWriter(PyObject* po_self, PyObject* args) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    try {
        PyObject* name_obj;
        const char* datasrc_name_p = "";
        if (PyArg_ParseTuple(args, "O!|s", &isc::dns::python::name_type,
                             &name_obj, &datasrc_name_p)) {
            const isc::dns::Name
                name(isc::dns::python::PyName_ToName(name_obj));
            const std::string datasrc_name(datasrc_name_p);

            const ConfigurableClientList::ZoneWriterPair result =
                self->cppobj->getCachedZoneWriter(name, datasrc_name);

            PyObjectContainer writer;
            if (!result.second) {
                // Use the Py_BuildValue, as it takes care of the
                // reference counts correctly.
                writer.reset(Py_BuildValue(""));
            } else {
                // Make sure it keeps the writer alive.
                writer.reset(createZoneWriterObject(result.second,
                                                    writer.get()));
            }

            return (Py_BuildValue("IO", result.first, writer.get()));
        } else {
            return (NULL);
        }
    } catch (const std::exception& exc) {
        PyErr_SetString(getDataSourceException("Error"), exc.what());
        return (NULL);
    } catch (...) {
        PyErr_SetString(getDataSourceException("Error"),
                        "Unknown C++ exception");
        return (NULL);
    }
}

PyObject*
ConfigurableClientList_find(PyObject* po_self, PyObject* args) {
    s_ConfigurableClientList* self =
        static_cast<s_ConfigurableClientList*>(po_self);
    try {
        PyObject* name_obj;
        int want_exact_match = 0;
        int want_finder = 1;
        if (PyArg_ParseTuple(args, "O!|ii", &isc::dns::python::name_type,
                             &name_obj, &want_exact_match, &want_finder)) {
            const isc::dns::Name
                name(isc::dns::python::PyName_ToName(name_obj));
            const ClientList::FindResult
                result(self->cppobj->find(name, want_exact_match,
                                          want_finder));
            PyObjectContainer dsrc;
            if (result.dsrc_client_ == NULL) {
                // Use the Py_BuildValue, as it takes care of the
                // reference counts correctly.
                dsrc.reset(Py_BuildValue(""));
            } else {
                // Make sure we have a keeper there too, so it doesn't
                // die when the underlying client list dies or is
                // reconfigured.
                //
                // However, as it is inside the C++ part, is there a
                // reasonable way to test it?
                dsrc.reset(wrapDataSourceClient(result.dsrc_client_,
                                                result.life_keeper_));
            }
            PyObjectContainer finder;
            if (result.finder_ == NULL) {
                finder.reset(Py_BuildValue(""));
            } else {
                // Make sure it keeps the data source client alive.
                finder.reset(createZoneFinderObject(result.finder_,
                                                    dsrc.get()));
            }
            PyObjectContainer exact(PyBool_FromLong(result.exact_match_));

            return (Py_BuildValue("OOO", dsrc.get(), finder.get(),
                                  exact.get()));
        } else {
            return (NULL);
        }
    } catch (const std::exception& exc) {
        PyErr_SetString(getDataSourceException("Error"), exc.what());
        return (NULL);
    } catch (...) {
        PyErr_SetString(getDataSourceException("Error"),
                        "Unknown C++ exception");
        return (NULL);
    }
}

// This list contains the actual set of functions we have in
// python. Each entry has
// 1. Python method name
// 2. Our static function here
// 3. Argument type
// 4. Documentation
PyMethodDef ConfigurableClientList_methods[] = {
    { "configure", ConfigurableClientList_configure, METH_VARARGS,
        "configure(configuration, allow_cache) -> None\n\
\n\
Wrapper around C++ ConfigurableClientList::configure\n\
\n\
This sets the active configuration. It fills the ConfigurableClientList with\
corresponding data source clients.\n\
\n\
If any error is detected, an exception is raised and the previous\
configuration preserved.\n\
\n\
Parameters:\n\
  configuration     The configuration, as a JSON encoded string.\
  allow_cache       If caching is allowed." },
    { "reset_memory_segment", ConfigurableClientList_resetMemorySegment,
      METH_VARARGS,
        "reset_memory_segment(datasrc_name, mode, config_params) -> None\n\
\n\
Wrapper around C++ ConfigurableClientList::resetMemorySegment\n\
\n\
This resets the zone table segment for a datasource with a new\n\
memory segment.\n\
\n\
Parameters:\n\
  datasrc_name      The name of the data source whose segment to reset.\
  mode              The open mode for the new memory segment.\
  config_params     The configuration for the new memory segment, as a JSON encoded string." },
    { "get_cached_zone_writer", ConfigurableClientList_getCachedZoneWriter,
      METH_VARARGS,
        "get_cached_zone_writer(zone, datasrc_name) -> status, zone_writer\n\
\n\
Wrapper around C++ ConfigurableClientList::getCachedZoneWriter\n\
\n\
This returns a ZoneWriter that can be used to (re)load a zone.\n\
\n\
Parameters:\n\
  zone              The name of the zone to (re)load.\
  datasrc_name      The name of the data source where the zone is to be loaded." },
    { "find", ConfigurableClientList_find, METH_VARARGS,
"find(zone, want_exact_match=False, want_finder=True) -> datasrc_client,\
zone_finder, exact_match\n\
\n\
Look for a data source containing the given zone.\n\
\n\
It searches through the contained data sources and returns a data source\
containing the zone, the zone finder of the zone and a boolean if the answer\
is an exact match.\n\
\n\
The first parameter is isc.dns.Name object of a name in the zone. If the\
want_exact_match is True, only zone with this exact origin is returned.\
If it is False, the best matching zone is returned.\n\
\n\
If the want_finder is False, the returned zone_finder might be None even\
if the data source is identified (in such case, the datasrc_client is not\
None). Setting it to false allows the client list some optimisations, if\
you don't need it, but if you do need it, it is better to set it to True\
instead of getting it from the datasrc_client later.\n\
\n\
If no answer is found, the datasrc_client and zone_finder are None." },
    { NULL, NULL, 0, NULL }
};

const char* const ConfigurableClientList_doc = "\
The list of data source clients\n\
\n\
The purpose is to have several data source clients of the same class\
and then be able to search through them to identify the one containing\
a given zone.\n\
\n\
Unlike the C++ version, we don't have the abstract base class. Abstract\
classes are not needed due to the duck typing nature of python.\
";

} // end of unnamed namespace

namespace isc {
namespace datasrc {
namespace python {
// This defines the complete type for reflection in python and
// parsing of PyObject* to s_ConfigurableClientList
// Most of the functions are not actually implemented and NULL here.
PyTypeObject configurableclientlist_type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "datasrc.ConfigurableClientList",
    sizeof(s_ConfigurableClientList),                 // tp_basicsize
    0,                                  // tp_itemsize
    ConfigurableClientList_destroy,                 // tp_dealloc
    NULL,                               // tp_print
    NULL,                               // tp_getattr
    NULL,                               // tp_setattr
    NULL,                               // tp_reserved
    NULL,                               // tp_repr
    NULL,                               // tp_as_number
    NULL,                               // tp_as_sequence
    NULL,                               // tp_as_mapping
    NULL,                               // tp_hash
    NULL,                               // tp_call
    NULL,                               // tp_str
    NULL,                               // tp_getattro
    NULL,                               // tp_setattro
    NULL,                               // tp_as_buffer
    Py_TPFLAGS_DEFAULT,                 // tp_flags
    ConfigurableClientList_doc,
    NULL,                               // tp_traverse
    NULL,                               // tp_clear
    NULL,                               // tp_richcompare
    0,                                  // tp_weaklistoffset
    NULL,                               // tp_iter
    NULL,                               // tp_iternext
    ConfigurableClientList_methods,                   // tp_methods
    NULL,                               // tp_members
    NULL,                               // tp_getset
    NULL,                               // tp_base
    NULL,                               // tp_dict
    NULL,                               // tp_descr_get
    NULL,                               // tp_descr_set
    0,                                  // tp_dictoffset
    ConfigurableClientList_init,                    // tp_init
    NULL,                               // tp_alloc
    PyType_GenericNew,                  // tp_new
    NULL,                               // tp_free
    NULL,                               // tp_is_gc
    NULL,                               // tp_bases
    NULL,                               // tp_mro
    NULL,                               // tp_cache
    NULL,                               // tp_subclasses
    NULL,                               // tp_weaklist
    NULL,                               // tp_del
    0                                   // tp_version_tag
};

// Module Initialization, all statics are initialized here
bool
initModulePart_ConfigurableClientList(PyObject* mod) {
    // We initialize the static description object with PyType_Ready(),
    // then add it to the module. This is not just a check! (leaving
    // this out results in segmentation faults)
    if (PyType_Ready(&configurableclientlist_type) < 0) {
        return (false);
    }
    void* p = &configurableclientlist_type;
    if (PyModule_AddObject(mod, "ConfigurableClientList",
                           static_cast<PyObject*>(p)) < 0) {
        return (false);
    }
    Py_INCREF(&configurableclientlist_type);

    try {
        // ConfigurableClientList::CacheStatus enum
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_CACHE_DISABLED",
                             Py_BuildValue("I", ConfigurableClientList::CACHE_DISABLED));
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_ZONE_NOT_CACHED",
                             Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_CACHED));
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_ZONE_NOT_FOUND",
                             Py_BuildValue("I", ConfigurableClientList::ZONE_NOT_FOUND));
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_CACHE_NOT_WRITABLE",
                             Py_BuildValue("I", ConfigurableClientList::CACHE_NOT_WRITABLE));
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_DATASRC_NOT_FOUND",
                             Py_BuildValue("I", ConfigurableClientList::DATASRC_NOT_FOUND));
        installClassVariable(configurableclientlist_type,
                             "CACHE_STATUS_ZONE_SUCCESS",
                             Py_BuildValue("I", ConfigurableClientList::ZONE_SUCCESS));

        // FIXME: These should eventually be moved to the
        // ZoneTableSegment class when we add Python bindings for the
        // memory data source specific bits. But for now, we add these
        // enums here to support reloading a zone table segment.
        installClassVariable(configurableclientlist_type, "CREATE",
                             Py_BuildValue("I", ZoneTableSegment::CREATE));
        installClassVariable(configurableclientlist_type, "READ_WRITE",
                             Py_BuildValue("I", ZoneTableSegment::READ_WRITE));
        installClassVariable(configurableclientlist_type, "READ_ONLY",
                             Py_BuildValue("I", ZoneTableSegment::READ_ONLY));
    } catch (const std::exception& ex) {
        const std::string ex_what =
            "Unexpected failure in ConfigurableClientList initialization: " +
            std::string(ex.what());
        PyErr_SetString(po_IscException, ex_what.c_str());
        return (false);
    } catch (...) {
        PyErr_SetString(PyExc_SystemError,
            "Unexpected failure in ConfigurableClientList initialization");
        return (false);
    }

    return (true);
}

} // namespace python
} // namespace datasrc
} // namespace isc