#include "dbxml_helper.h"

#include "XmlManager.hpp"
#include "XmlException.hpp"

#include <string.h>

using namespace DbXml;
using namespace std;

#define UNWRAP_DBENV() \
    DB_ENV* dbenv; \
    if (*dbenvp == NULL) return -20881;  /* Haskell binding-specific error code */ \
    dbenv = *dbenvp;

#define UNWRAP_DBTXN_() \
    if (dbtxnp == NULL) \
        dbtxn = NULL; \
    else { \
        dbtxn = *dbtxnp; \
        if (dbtxn == NULL) return -20883;  /* Haskell binding-specific error code */ \
    }

#define UNWRAP_DBTXN() \
    DB_TXN* dbtxn; \
    UNWRAP_DBTXN_()

XmlManagerInfo::XmlManagerInfo()
{
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
    pthread_mutex_init(&mutex, &attr);
}

XmlManagerInfo::~XmlManagerInfo()
{
    while (queryExpressions.begin() != queryExpressions.end())
        delete *queryExpressions.begin();
}

XmlQueryExpressionInfo::XmlQueryExpressionInfo(
    DbXml::XmlQueryExpression qe,
    XmlManagerInfo* mi)
    : qe(qe), mi(mi)
{
    pthread_mutex_lock(&mi->mutex);
    try {
        mi->queryExpressions.insert(this);
        pthread_mutex_unlock(&mi->mutex);
    }
    catch (...) {
        pthread_mutex_unlock(&mi->mutex);
        throw;
    }
}

XmlQueryExpressionInfo::~XmlQueryExpressionInfo()
{
    pthread_mutex_lock(&mi->mutex);
    try {
        std::set<XmlQueryExpressionInfo*>::iterator it = mi->queryExpressions.find(this);
        if (it != mi->queryExpressions.end())
            mi->queryExpressions.erase(it);
        pthread_mutex_unlock(&mi->mutex);
    }
    catch (...) {
        pthread_mutex_unlock(&mi->mutex);
        throw;
    }
}

static int asInt(XmlException& exc)
{
    int dbE = exc.getDbErrno();
    if (dbE < 0)
        dbE = -dbE;
    return (exc.getExceptionCode() + 1) * 100000 + dbE;
}

int _xmlManager(DB_ENV** dbenvp, u_int32_t flags, XmlManagerInfo** mgr)
{
    UNWRAP_DBENV();
    *mgr = new XmlManagerInfo;
    try {
        (*mgr)->mgr = XmlManager(dbenv, flags);
        return 0;
    }
    catch (XmlException& exc) {
        delete *mgr;
        *mgr = NULL;
        return asInt(exc);
    }
}

int _xmlManager_toNative(XmlManagerInfo* mgr, DbXml::XmlManager** pMgr)
{
    *pMgr = &mgr->mgr;
    return 0;
}

void _xmlManager_close(XmlManagerInfo* mgr)
{
    delete mgr;
}

int _xmlManager_openContainer(XmlManagerInfo* mgr, const char* name, u_int32_t flags,
    int cType, int mode, DbXml::XmlContainer*** container)
{
    try {
        DbXml::XmlContainer c = mgr->mgr.openContainer(name, flags,
            cType == 0 ? XmlContainer::NodeContainer : XmlContainer::WholedocContainer,
            mode);
        *container = new DbXml::XmlContainer*;
        **container = new DbXml::XmlContainer(c);
        return 0;
    }
    catch (XmlException& exc) {
        *container = NULL;
        return asInt(exc);
    }
}

int _xmlManager_existsContainer(XmlManagerInfo* mgr, const char* name)
{
    return mgr->mgr.existsContainer(name);
}

void _xmlContainer_delete(DbXml::XmlContainer** cont)
{
    delete *cont;
    delete cont;
}

// Delete the container reference but not the container itself, because someone
// else is managing it.
void _xmlContainer_nullDelete(DbXml::XmlContainer** cont)
{
    delete cont;
}

void _xmlContainer_close(DbXml::XmlContainer** cont)
{
    delete *cont;
    *cont = NULL;
}

int _xmlManager_createTransaction(XmlManagerInfo* mgr, u_int32_t flags, DbXml::XmlTransaction** trans)
{
    try {
        XmlTransaction t = mgr->mgr.createTransaction(flags);
        *trans = new XmlTransaction(t);
        return 0;
    }
    catch (XmlException& exc) {
        *trans = NULL;
        return asInt(exc);
    }
}

int _xmlManager_createTransaction_DbTxn(XmlManagerInfo* mgr, DB_TXN** dbtxnp, DbXml::XmlTransaction** trans)
{
    UNWRAP_DBTXN();
    try {
        *dbtxnp = NULL;  // Detach from Berkeley DB so we now take responsibility
                         // for cleaning it up.
        XmlTransaction t = mgr->mgr.createTransaction(dbtxn);
        *trans = new XmlTransaction(t);
        return 0;
    }
    catch (XmlException& exc) {
        *trans = NULL;
        return asInt(exc);
    }
}

void _xmlTransaction_delete(DbXml::XmlTransaction* trans)
{
    delete trans;
}

void _xmlTransaction_nullDestructor(DbXml::XmlTransaction* trans)
{
}

DbXml::XmlTransaction* _xmlTransaction_fromNative(DbXml::XmlTransaction* trans)
{
    return trans;
}

int _xmlTransaction_commit(DbXml::XmlTransaction* trans)
{
    try {
        trans->commit();
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlTransaction_abort(DbXml::XmlTransaction* trans)
{
    try {
        trans->abort();
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlContainer_toNative(DbXml::XmlContainer** cont, DbXml::XmlContainer** pCont)
{
    *pCont = *cont;
    return 0;
}

int _xmlContainer_fromNative(DbXml::XmlContainer* cont, DbXml::XmlContainer*** container)
{
    *container = new DbXml::XmlContainer*;
    **container = cont;
    return 0;
}

int _xmlContainer_getDocument(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
    const char* key, u_int32_t flags, DbXml::XmlDocument** doc)
{
    try {
        XmlDocument d;
        if (trans != NULL)
            d = (*cont)->getDocument(*trans, key, flags);
        else
            d = (*cont)->getDocument(key, flags);
        *doc = new XmlDocument(d);
        return 0;
    }
    catch (XmlException& exc) {
        *doc = NULL;
        return asInt(exc);
    }
}

char* _xmlContainer_getName(DbXml::XmlContainer** cont)
{
    string s = (*cont)->getName();
    char* str = new char[s.length()+1];
    strcpy(str, s.c_str());
    return str;
}

void _deleteString(char* str)
{
    delete[] str;
}

void _xmlDocument_delete(DbXml::XmlDocument* doc)
{
    delete doc;
}

int _xmlDocument_getContent(DbXml::XmlDocument* doc, char** str, int* length)
{
    string s;
    try {
        doc->getContent(s);
    }
    catch (XmlException& exc) {
        *str = NULL;
        return asInt(exc);
    }
    *length = (int)s.length();
    *str = new char[s.length()+1];
    strcpy(*str, s.c_str());
    return 0;
}

int _xmlManager_createQueryContext(XmlManagerInfo* mgr, int rt, int ev, DbXml::XmlQueryContext** ctx)
{
    try {
        XmlQueryContext c = mgr->mgr.createQueryContext(
            XmlQueryContext::LiveValues,
            ev == 0 ? XmlQueryContext::Eager : XmlQueryContext::Lazy);
        *ctx = new XmlQueryContext(c);
        return 0;
    }
    catch (XmlException& exc) {
        *ctx = NULL;
        return asInt(exc);
    }
}

void _xmlQueryContext_delete(DbXml::XmlQueryContext* ctx)
{
    delete ctx;
}

void _xmlQueryContext_nullDestructor(DbXml::XmlQueryContext*)
{
}

DbXml::XmlQueryContext* _xmlQueryContext_fromNative(DbXml::XmlQueryContext* qc)
{
    return qc;
}

void _xmlResults_delete(DbXml::XmlResults* res)
{
    delete res;
}

int _xmlResults_hasNext(DbXml::XmlResults* res, int* answer)
{
    try {
        *answer = res->hasNext();
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlResults_nextDocument(DbXml::XmlResults* res, DbXml::XmlDocument** doc)
{
    *doc = new XmlDocument;
    try {
        if (!res->next(**doc)) {
            delete *doc;
            *doc = NULL;
        }
        return 0;
    }
    catch (XmlException& exc) {
        delete *doc;
        *doc = NULL;
        return asInt(exc);
    }
}

int _xmlResults_nextValue(DbXml::XmlResults* res, DbXml::XmlValue** val)
{
    *val = new XmlValue;
    try {
        if (!res->next(**val)) {
            delete *val;
            *val = NULL;
        }
        return 0;
    }
    catch (XmlException& exc) {
        delete *val;
        *val = NULL;
        return asInt(exc);
    }
}

int _xmlManager_query(XmlManagerInfo* mgr, DbXml::XmlTransaction* trans,
    const char* query, DbXml::XmlQueryContext* ctx, u_int32_t flags, DbXml::XmlResults** res)
{
    try {
        XmlResults r;
        if (trans != NULL)
            r = mgr->mgr.query(*trans, query, *ctx, flags);
        else
            r = mgr->mgr.query(query, *ctx, flags);
        *res = new XmlResults(r);
        return 0;
    }
    catch (XmlException& exc) {
        *res = NULL;
        return asInt(exc);
    }
}

void _xmlQueryExpression_delete(XmlQueryExpressionInfo* exp)
{
    delete exp;
}

int _xmlManager_prepare(XmlManagerInfo* mgr, DbXml::XmlTransaction* trans,
    const char* query, DbXml::XmlQueryContext* ctx, XmlQueryExpressionInfo** exp)
{
    try {
        XmlQueryExpression e;
        if (trans != NULL)
            e = mgr->mgr.prepare(*trans, query, *ctx);
        else
            e = mgr->mgr.prepare(query, *ctx);
        *exp = new XmlQueryExpressionInfo(e, mgr);
        return 0;
    }
    catch (XmlException& exc) {
        *exp = NULL;
        return asInt(exc);
    }
}

int _xmlQueryContext_setDefaultCollection(DbXml::XmlQueryContext* ctx, const char* coll)
{
    try {
        ctx->setDefaultCollection(coll);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlQueryContext_setVariableValue(DbXml::XmlQueryContext* ctx, const char* name, DbXml::XmlValue* value)
{
    try {
        ctx->setVariableValue(name, *value);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

void _xmlValue_delete(DbXml::XmlValue* val)
{
    delete val;
}

DbXml::XmlValue* _xmlNone()
{
    return new XmlValue;
}

DbXml::XmlValue* _xmlString(const char* text)
{
    return new XmlValue(text);
}

DbXml::XmlValue* _xmlBool(int value)
{
    return new XmlValue((bool)value);
}

DbXml::XmlValue* _xmlDouble(double value)
{
    return new XmlValue(value);
}

DbXml::XmlValue* _xmlXmlDocument(DbXml::XmlDocument* doc)
{
    return new XmlValue(*doc);
}

int _xmlValue_asString(DbXml::XmlValue* value, char** str, int* length)
{
    string s;
    try {
        s = value->asString();
    }
    catch (XmlException& exc) {
        *str = NULL;
        return asInt(exc);
    }
    *length = (int)s.length();
    *str = new char[s.length()+1];
    strcpy(*str, s.c_str());
    return 0;
}

int _xmlQueryExpression_execute(XmlQueryExpressionInfo* exp, DbXml::XmlTransaction* trans,
    DbXml::XmlValue* contextItem, DbXml::XmlQueryContext* qctx, u_int32_t flags, DbXml::XmlResults** res)
{
    try {
        XmlResults r;
        if (trans != NULL && contextItem != NULL)
            r = exp->qe.execute(*trans, *contextItem, *qctx, flags);
        else
        if (trans != NULL && contextItem == NULL)
            r = exp->qe.execute(*trans, *qctx, flags);
        else
        if (trans == NULL && contextItem != NULL)
            r = exp->qe.execute(*contextItem, *qctx, flags);
        else
            r = exp->qe.execute(*qctx, flags);
        *res = new XmlResults(r);
        return 0;
    }
    catch (XmlException& exc) {
        *res = NULL;
        return asInt(exc);
    }
}

int _xmlManager_createDocument(XmlManagerInfo* mgr, DbXml::XmlDocument** doc)
{
    try {
        XmlDocument d = mgr->mgr.createDocument();
        *doc = new XmlDocument(d);
        return 0;
    }
    catch (XmlException& exc) {
        *doc = NULL;
        return asInt(exc);
    }
}

int _xmlDocument_getName(DbXml::XmlDocument* doc, char** name)
{
    string s;
    try {
        s = doc->getName();
    }
    catch (XmlException& exc) {
        *name = NULL;
        return asInt(exc);
    }
    *name = new char[s.length()+1];
    strcpy(*name, s.c_str());
    return 0;
}

int _xmlDocument_setName(DbXml::XmlDocument* doc, const char* name)
{
    try {
        doc->setName(name);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

void _xmlUpdateContext_delete(DbXml::XmlUpdateContext* ctx)
{
    delete ctx;
}

void _xmlUpdateContext_nullDestructor(DbXml::XmlUpdateContext*)
{
}

DbXml::XmlUpdateContext* _xmlUpdateContext_fromNative(DbXml::XmlUpdateContext* uc)
{
    return uc;
}

int _xmlManager_createUpdateContext(XmlManagerInfo* mgr, DbXml::XmlUpdateContext** ctx)
{
    try {
        XmlUpdateContext c = mgr->mgr.createUpdateContext();
        *ctx = new XmlUpdateContext(c);
        return 0;
    }
    catch (XmlException& exc) {
        *ctx = NULL;
        return asInt(exc);
    }
}

int _xmlDocument_setContent(DbXml::XmlDocument* doc, const char* text, unsigned int length)
{
    try {
        doc->setContent(string(text, length));
        return 0;
    }
    catch (XmlException& exc) {
        *doc = NULL;
        return asInt(exc);
    }
}

int _xmlContainer_updateDocument(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
    DbXml::XmlDocument* doc, DbXml::XmlUpdateContext* uctx)
{
    try {
        if (trans != NULL)
            (*cont)->updateDocument(*trans, *doc, *uctx);
        else
            (*cont)->updateDocument(*doc, *uctx);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlContainer_putDocument(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
    DbXml::XmlDocument* doc, DbXml::XmlUpdateContext* uctx, u_int32_t flags)
{
    try {
        if (trans != NULL)
            (*cont)->putDocument(*trans, *doc, *uctx, flags);
        else
            (*cont)->putDocument(*doc, *uctx, flags);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlContainer_deleteDocument(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
    DbXml::XmlDocument* doc, DbXml::XmlUpdateContext* uctx)
{
    try {
        if (trans != NULL)
            (*cont)->deleteDocument(*trans, *doc, *uctx);
        else
            (*cont)->deleteDocument(*doc, *uctx);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlDocument_setMetaData(DbXml::XmlDocument* doc, const char* uri , const char* name, DbXml::XmlValue* value)
{
    try {
        string uri_ (uri);
        string name_ (name);
        doc->setMetaData(uri_, name_, *value);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlContainer_addIndex(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
        const char* uri, const char* name, const char* index, DbXml::XmlUpdateContext* uctx)
{
    try {
        if (trans != NULL)
            (*cont)->addIndex(*trans, uri, name, index, *uctx);
        else
            (*cont)->addIndex(uri, name, index, *uctx);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

int _xmlContainer_deleteIndex(DbXml::XmlContainer** cont, DbXml::XmlTransaction* trans,
        const char* uri, const char* name, const char* index, DbXml::XmlUpdateContext* uctx)
{
    try {
        if (trans != NULL)
            (*cont)->deleteIndex(*trans, uri, name, index, *uctx);
        else
            (*cont)->deleteIndex(uri, name, index, *uctx);
        return 0;
    }
    catch (XmlException& exc) {
        return asInt(exc);
    }
}

