/*
  This file is part of libkldap.
  SPDX-FileCopyrightText: 2004-2006 Szombathelyi György <gyurco@freemail.hu>

  SPDX-License-Identifier: LGPL-2.0-or-later
*/

#include "ber.h"
#include "kldap_config.h"

#include "ldap_core_debug.h"

#include <QList>
#include <QVarLengthArray>

#include <cstdarg>

#if LDAP_FOUND

#ifdef Q_OS_SOLARIS // krazy:exclude=cpp
#define BC31 1
#endif

#if !HAVE_WINLDAP_H
#include <lber.h>
#include <ldap.h>
#else
#include "w32-ldap-help.h"
#endif

#ifndef LBER_USE_DER
#define LBER_USE_DER 1
#endif

#if !HAVE_BER_MEMFREE
#if !HAVE_WINLDAP_H
#define ber_memfree(x) ldap_memfree(x)
#else
#define ber_memfree(x) win_ldap_memfree(x)
#endif
#endif

#endif

using namespace KLDAPCore;

class Q_DECL_HIDDEN Ber::BerPrivate
{
public:
#if LDAP_FOUND
    BerElement *mBer = nullptr;
#endif
};

#if LDAP_FOUND
Ber::Ber()
    : d(new BerPrivate)
{
    d->mBer = ber_alloc_t(LBER_USE_DER);
    Q_ASSERT(d->mBer);
}

Ber::Ber(const QByteArray &value)
    : d(new BerPrivate)
{
    struct berval bv;
    bv.bv_val = (char *)value.data();
    bv.bv_len = value.size();
    d->mBer = ber_init(&bv);
    Q_ASSERT(d->mBer);
}

Ber::~Ber()
{
    ber_free(d->mBer, 1);
}

Ber::Ber(const Ber &that)
    : d(new BerPrivate)
{
    struct berval *bv;
    if (ber_flatten(that.d->mBer, &bv) == 0) {
        d->mBer = ber_init(bv);
        ber_bvfree(bv);
    }
}

Ber &Ber::operator=(const Ber &that)
{
    if (this == &that) {
        return *this;
    }

    struct berval *bv;
    if (ber_flatten(that.d->mBer, &bv) == 0) {
        ber_free(d->mBer, 1);
        d->mBer = ber_init(bv);
        ber_bvfree(bv);
    }
    return *this;
}

QByteArray Ber::flatten() const
{
    QByteArray ret;
    struct berval *bv;
    if (ber_flatten(d->mBer, &bv) == 0) {
        ret = QByteArray(bv->bv_val, bv->bv_len);
        ber_bvfree(bv);
    }
    return ret;
}

int Ber::printf(QString format, ...)
{
    char fmt[2];
    va_list args;
    va_start(args, format);
    fmt[1] = '\0';

    int i = 0;
    int ret = 0;
    while (i < format.length()) {
        fmt[0] = format[i].toLatin1();
        i++;
        switch (fmt[0]) {
        case 'b':
        case 'e':
        case 'i': {
            ber_int_t v = va_arg(args, int);
            ret = ber_printf(d->mBer, fmt, v);
            break;
        }
        case 'B': {
            // FIXME: QBitArray would be logical, but how to access the bits?
            QByteArray *B = va_arg(args, QByteArray *);
            int Bc = va_arg(args, int);
            ret = ber_printf(d->mBer, fmt, B->data(), Bc);
            break;
        }
        case 'o': {
            QByteArray *o = va_arg(args, QByteArray *);
            ret = ber_printf(d->mBer, fmt, o->data(), o->size());
            break;
        }
        case 'O': {
            QByteArray *O = va_arg(args, QByteArray *);
            struct berval bv;
            bv.bv_val = (char *)O->data();
            bv.bv_len = O->size();
            ret = ber_printf(d->mBer, fmt, &bv);
            break;
        }
        case 's': {
            QByteArray *s = va_arg(args, QByteArray *);
            ret = ber_printf(d->mBer, fmt, s->data());
            break;
            break;
        }
        case 't': {
            unsigned int t = va_arg(args, unsigned int);
            ret = ber_printf(d->mBer, fmt, t);
            break;
        }
        case 'v': {
            QList<QByteArray> *v = va_arg(args, QList<QByteArray> *);
            QVarLengthArray<const char *> l(v->count() + 1);
            int j;
            for (j = 0; j < v->count(); j++) {
                l[j] = v->at(j).data();
            }
            l[j] = nullptr;
            ret = ber_printf(d->mBer, fmt, l.data());
            break;
        }
        case 'V': {
            QList<QByteArray> *V = va_arg(args, QList<QByteArray> *);
            QVarLengthArray<struct berval *> bv(V->count() + 1);
            QVarLengthArray<struct berval> bvs(V->count());
            int j;
            for (j = 0; j < V->count(); j++) {
                bvs[j].bv_val = (char *)V->at(j).data();
                bvs[j].bv_len = V->at(j).size();
                bv[j] = &bvs[j];
            }
            bv[V->count()] = nullptr;
            ret = ber_printf(d->mBer, fmt, bv.data());
            break;
        }
        case 'n':
        case '{':
        case '}':
        case '[':
        case ']':
            ret = ber_printf(d->mBer, fmt);
            break;
        default:
            qCWarning(LDAP_CORE_LOG) << "Invalid BER format parameter: '" << fmt << "'";
            ret = -1;
        }
        qCDebug(LDAP_CORE_LOG) << "ber_printf format:" << fmt << "ret:" << ret;
        if (ret == -1) {
            break;
        }
    }
    va_end(args);
    return ret;
}

int Ber::scanf(QString format, ...)
{
    char fmt[2];
    va_list args;
    va_start(args, format);
    fmt[1] = '\0';

    int i = 0;
    int ret = 0;
    while (i < format.length()) {
        fmt[0] = format[i].toLatin1();
        i++;
        switch (fmt[0]) {
        case 'l':
        case 'b':
        case 'e':
        case 'i': {
            int *v = va_arg(args, int *);
            ret = ber_scanf(d->mBer, fmt, v);
            break;
        }
        case 'B': {
            // FIXME: QBitArray would be logical, but how to access the bits?
            QByteArray *B = va_arg(args, QByteArray *);
            int *Bc = va_arg(args, int *);
            char *c;
            ret = ber_scanf(d->mBer, fmt, &c, Bc);
            if (ret != -1) {
                *B = QByteArray(c, (*Bc + 7) / 8);
                ber_memfree(c);
            }
            break;
        }
        case 'o': {
            QByteArray *o = va_arg(args, QByteArray *);
            struct berval bv;
            ret = ber_scanf(d->mBer, fmt, &bv);
            if (ret != -1) {
                *o = QByteArray(bv.bv_val, bv.bv_len);
                ber_memfree(bv.bv_val);
            }
            break;
        }
        case 'O': {
            QByteArray *O = va_arg(args, QByteArray *);
            struct berval *bv;
            ret = ber_scanf(d->mBer, fmt, &bv);
            if (ret != -1) {
                *O = QByteArray(bv->bv_val, bv->bv_len);
                ber_bvfree(bv);
            }
            break;
        }
        case 'm': { // the same as 'O', just *bv should not be freed.
            QByteArray *m = va_arg(args, QByteArray *);
            struct berval *bv;
            ret = ber_scanf(d->mBer, fmt, &bv);
            if (ret != -1) {
                *m = QByteArray(bv->bv_val, bv->bv_len);
            }
            break;
        }
        case 'a': {
            QByteArray *a = va_arg(args, QByteArray *);
            char *c;
            ret = ber_scanf(d->mBer, fmt, &c);
            if (ret != -1) {
                *a = QByteArray(c);
                ber_memfree(c);
            }
            break;
        }

        case 's': {
            QByteArray *s = va_arg(args, QByteArray *);
            char buf[255];
            ber_len_t l = sizeof(buf);
            ret = ber_scanf(d->mBer, fmt, &buf, &l);
            if (ret != -1) {
                *s = QByteArray(buf, l);
            }
            break;
        }
        case 't':
        case 'T': {
            unsigned int *t = va_arg(args, unsigned int *);
            ret = ber_scanf(d->mBer, fmt, t);
            break;
        }
        case 'v': {
            QList<QByteArray> *v = va_arg(args, QList<QByteArray> *);
            char **c;
            char **c2;
            ret = ber_scanf(d->mBer, fmt, &c);
            if (ret != -1 && c) {
                c2 = c;
                while (*c) {
                    v->append(QByteArray(*c));
                    ber_memfree(*c);
                    c++;
                }
                ber_memfree((char *)c2);
            }
            break;
        }
        case 'V': {
            QList<QByteArray> *v = va_arg(args, QList<QByteArray> *);
            struct berval **bv;
            struct berval **bv2;
            ret = ber_scanf(d->mBer, fmt, &bv);
            if (ret != -1 && bv) {
                bv2 = bv;
                while (*bv) {
                    v->append(QByteArray((*bv)->bv_val, (*bv)->bv_len));
                    bv++;
                }
                ber_bvecfree(bv2);
            }
            break;
        }
        case 'x':
        case 'n':
        case '{':
        case '}':
        case '[':
        case ']':
            ret = ber_scanf(d->mBer, fmt);
            break;
        default:
            qCWarning(LDAP_CORE_LOG) << "Invalid BER format parameter: '" << fmt << "'";
            ret = -1;
        }

        qCDebug(LDAP_CORE_LOG) << "ber_scanf format:" << fmt << "ret:" << ret;
        if (ret == -1) {
            break;
        }
    }
    va_end(args);
    return ret;
}

unsigned int Ber::peekTag(int &size)
{
    unsigned int ret;
    ber_len_t len;
    ret = ber_peek_tag(d->mBer, &len);
    size = len;
    return ret;
}

unsigned int Ber::skipTag(int &size)
{
    unsigned int ret;
    ber_len_t len;
    ret = ber_skip_tag(d->mBer, &len);
    size = len;
    return ret;
}

#else

Ber::Ber()
    : d(new BerPrivate)
{
    qCritical() << "LDAP support not compiled";
}

Ber::Ber(const QByteArray &)
    : d(new BerPrivate)
{
    qCritical() << "LDAP support not compiled";
}

Ber::~Ber()
{
}

Ber::Ber(const Ber &)
    : d(new BerPrivate)
{
    qCritical() << "LDAP support not compiled";
}

Ber &Ber::operator=(const Ber &that)
{
    if (this == &that) {
        return *this;
    }
    qCritical() << "LDAP support not compiled";
    return *this;
}

QByteArray Ber::flatten() const
{
    qCritical() << "LDAP support not compiled";
    return QByteArray();
}

int Ber::printf([[maybe_unused]] QString format, ...)
{
    qCritical() << "LDAP support not compiled";
    return -1;
}

int Ber::scanf([[maybe_unused]] QString format, ...)
{
    qCritical() << "LDAP support not compiled";
    return -1;
}

unsigned int Ber::peekTag([[maybe_unused]] int &size)
{
    qCritical() << "LDAP support not compiled";
    return 0;
}

unsigned int Ber::skipTag([[maybe_unused]] int &size)
{
    qCritical() << "LDAP support not compiled";
    return 0;
}

#endif
