/*
 * libkysdk-appconf2's Library
 *
 * Copyright (C) 2024, KylinSoft Co., Ltd.
 *
 * 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 3 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 General Public License
 * along with this library.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Authors: Zhenyu Wang <wangzhenyu@kylinos.cn>
 *
 */

#include "kconf2.h"
#include <QDebug>
#include <QByteArray>
#include <string>
#include <iostream>
#include <libkysettings.h>
namespace kdk {

enum KCONF2TYPE {
    KCONF2_TYPE_BOOLEAN       = 'b',
    KCONF2_TYPE_BYTE          = 'y',
    KCONF2_TYPE_INT16         = 'n',
    KCONF2_TYPE_UINT16        = 'q',
    KCONF2_TYPE_INT32         = 'i',
    KCONF2_TYPE_UINT32        = 'u',
    KCONF2_TYPE_INT64         = 'x',
    KCONF2_TYPE_UINT64        = 't',
    KCONF2_TYPE_HANDLE        = 'h',
    KCONF2_TYPE_DOUBLE        = 'd',
    KCONF2_TYPE_STRING        = 's',
    KCONF2_TYPE_OBJECT_PATH   = 'o',
    KCONF2_TYPE_SIGNATURE     = 'g',
    KCONF2_TYPE_VARIANT       = 'v',
    KCONF2_TYPE_MAYBE         = 'm',
    KCONF2_TYPE_ARRAY         = 'a',
    KCONF2_TYPE_TUPLE         = '(',
    KCONF2_TYPE_DICT_ENTRY    = '{'
};

class KConf2Private : public QObject
{
    Q_OBJECT
    Q_DECLARE_PUBLIC(KConf2)

public:
    KConf2Private(KConf2* parent);
    QVariant typesToQvariant(const char *key, const char *value) const;
    QVariant stringListToQvariant(char **strList) const;
    char** strvFromQVariant(const QVariant& variant);

private:
    KConf2 *q_ptr;
    KSettings* m_pGsettings = nullptr;
    long signal_handler_id;
};

void k_free(char **str)
{
    for (int i = 0; str[i]; i++)
        free(str[i]);
}

QString qtifyName(const char *name)
{
    bool next_cap = false;
    QString result;

    while (*name) {
        if (*name == '-') {
            next_cap = true;
        } else if (next_cap) {
            result.append(QChar(*name).toUpper().toLatin1());
            next_cap = false;
        } else {
            result.append(*name);
        }

        name++;
    }

    return result;
}

static void settingChanged(KSettings *ukuistyle_settings, const char *key, void *user_data)
{
    KConf2 *self = (KConf2 *)user_data;

    QMetaObject::invokeMethod(self, "changed", Qt::QueuedConnection, Q_ARG(QString, qtifyName(key)));
}

KConf2::KConf2(const QByteArray &schema_id, const QString version, QObject *parent)
    :d_ptr(new KConf2Private(this)),
      QObject(parent)
{
    Q_D(KConf2);
    if (!d->m_pGsettings)
    {
        if(version.isEmpty())
            d->m_pGsettings = kdk_conf2_new(schema_id, NULL);
        else
            d->m_pGsettings = kdk_conf2_new(schema_id, version.toStdString().c_str());
    }
    d->signal_handler_id = kdk_conf2_connect_signal(d->m_pGsettings, "changed",(KCallBack)settingChanged, this);
}

KConf2::~KConf2()
{
    Q_D(KConf2);
    kdk_conf2_ksettings_destroy(d->m_pGsettings);
}

QVariant KConf2::get(const QString &key) const
{
    Q_D(const KConf2);

    char*type = kdk_conf2_get_type(d->m_pGsettings, key.toUtf8().data());
    if(strcmp(type, "as") == 0)
    {
       char** strList = kdk_conf2_get_strv(d->m_pGsettings, key.toUtf8().data());
       if(strList == NULL)
       {
            free(type);
            free(strList);
            qWarning()<<"There is no value for key";
       }
       else
       {
           QVariant qvalue = d->stringListToQvariant(strList);
           free(strList);
           free(type);
           return qvalue;
       }
    }
    else
    {
        char *value = kdk_conf2_get_value(d->m_pGsettings, key.toUtf8().data());

        if(value == NULL )
        {
            qWarning()<<"There is no value for key";
            free(value);
            free(type);
            return QVariant();
        }
        else
        {
            QVariant qvalue = d->typesToQvariant(key.toUtf8().data(), value);
            free(value);
            free(type);
            return qvalue;
        }
    }
}

void KConf2::set(const QString &key, const QVariant &value)
{
    if (!this->trySet(key, value))
        qWarning("unable to set key '%s' to value '%s'", key.toUtf8().constData(), value.toString().toUtf8().constData());
}

bool KConf2::trySet(const QString &key, const QVariant &value)
{
    Q_D(KConf2);
    if (!d->m_pGsettings)
        return false;

    bool success = false;

    char *type = kdk_conf2_get_type(d->m_pGsettings, key.toUtf8().data());
    if(strcmp(type, "as") == 0)
    {
        char** strv = d->strvFromQVariant(value);
        success = kdk_conf2_set_strv(d->m_pGsettings, key.toStdString().c_str(), strv);
    }
    else
    {
        switch (*type) {
        case KCONF2_TYPE_BOOLEAN:
        {
            qDebug()<<value.toInt();
            success = kdk_conf2_set_boolean(d->m_pGsettings, key.toUtf8().data(), 0);
            break;
        }
        case KCONF2_TYPE_INT32:
            success = kdk_conf2_set_int(d->m_pGsettings, key.toUtf8().data(), value.toInt());
            break;
        case KCONF2_TYPE_INT64:
            success = kdk_conf2_set_int64(d->m_pGsettings, key.toUtf8().data(), value.toLongLong());
            break;
        case KCONF2_TYPE_UINT64:
            success = kdk_conf2_set_uint64(d->m_pGsettings, key.toUtf8().data(), value.toULongLong());
            break;
        case KCONF2_TYPE_DOUBLE:
            success = kdk_conf2_set_double(d->m_pGsettings, key.toUtf8().data(), value.toDouble());
            break;
        case KCONF2_TYPE_STRING:
        default:
            success = kdk_conf2_set_string(d->m_pGsettings, key.toUtf8().data(), value.toString().toStdString().c_str());
            break;
        }
    }
    return success;
}

QStringList KConf2::keys() const
{
    Q_D(const KConf2);
    QStringList list;

    char **keys = kdk_conf2_list_keys(d->m_pGsettings);
    for (int i = 0; keys[i]; i++)
        list.append(qtifyName(keys[i]));

    k_free(keys);

    return list;
}

void KConf2::reset(const QString &qkey)
{
    Q_D(KConf2);
    if (!d->m_pGsettings)
        return;

    kdk_conf2_reset(d->m_pGsettings, qkey.toUtf8().data());
}

bool KConf2::isSettingsAvailable(const QString &schema_id)
{
    bool result = false;
    if (kdk_conf2_new(schema_id.toUtf8().data(), NULL))
    {
        result = true;
    }
    return result;
}

QStringList KConf2::schemas(const QString &app, const QString &version) const
{
    Q_D(const KConf2);
    QStringList list;

    char **schemas = kdk_conf2_list_schemas(app.toUtf8().data(),version.toUtf8().data());
    if(schemas == NULL)
    {
        qWarning()<<"The obtained list schemas is empty";
    }
    else
    {
        for (int i = 0; schemas[i]; i++)
            list.append(qtifyName(schemas[i]));

        k_free(schemas);
    }
    return list;
}

QString KConf2::getDescrition(QString &key)
{
    Q_D(KConf2);
    char *value = kdk_conf2_get_descrition(d->m_pGsettings, key.toUtf8().data());

    QString str = QString::fromUtf8(value);

    free(value);
    return str;
}

QString KConf2::getSummary(QString &key)
{
    Q_D(KConf2);
    char *value = kdk_conf2_get_summary(d->m_pGsettings, key.toUtf8().data());

    QString str = QString::fromUtf8(value);

    free(value);

    return str;
}

QString KConf2::getType(QString &key)
{
    Q_D(KConf2);
    char *value = kdk_conf2_get_type(d->m_pGsettings, key.toUtf8().data());

    QString str = QString::fromUtf8(value);

    free(value);
    return str;
}

bool KConf2::isWritable(QString &key)
{
    Q_D(KConf2);
    if (!d->m_pGsettings)
        return false;

    bool success = false;

    success = kdk_conf2_is_writable(d->m_pGsettings, key.toUtf8().data());
    return success;
}

QString KConf2::getId()
{
    Q_D(KConf2);
    char *value = kdk_conf2_get_id(d->m_pGsettings);
    QString str = QString::fromUtf8(value);

    free(value);
    return str;
}

QString KConf2::getVersion()
{
    Q_D(KConf2);
    char *value = kdk_conf2_get_version(d->m_pGsettings);
    QString str = QString::fromUtf8(value);

    free(value);
    return str;
}

QStringList KConf2::children() const
{
    Q_D(const KConf2);
    QStringList list;

    char **keys = kdk_conf2_list_children(d->m_pGsettings);
    for (int i = 0; keys[i]; i++)
        list.append(qtifyName(keys[i]));

    k_free(keys);

    return list;
}

KConf2 *KConf2::getChild(QString &name)
{
    Q_D(KConf2);
    KSettings *settings = kdk_conf2_get_child(d->m_pGsettings, name.toUtf8().data());
    KConf2* conf = new KConf2(kdk_conf2_get_id(settings));
    return conf;
}

KConf2Private::KConf2Private(KConf2 *parent)
    :q_ptr(parent)
{
    setParent(parent);
}

QVariant KConf2Private::typesToQvariant(const char *key, const char *value) const
{
    Q_Q(const KConf2);
    char *type = kdk_conf2_get_type(m_pGsettings, key);
        switch (*type)
        {
        case KCONF2_TYPE_BOOLEAN:
            return QVariant(value);

        case KCONF2_TYPE_BYTE:
            return QVariant(QString(value));

        case KCONF2_TYPE_INT16:
            return QVariant(QString(value).toShort());

        case KCONF2_TYPE_UINT16:
            return QVariant(QString(value).toUShort());

        case KCONF2_TYPE_INT32:
            return QVariant(QString(value).toInt());

        case KCONF2_TYPE_UINT32:
            return QVariant(QString(value).toUInt());

        case KCONF2_TYPE_INT64:
            return QVariant(QString(value).toLongLong());

        case KCONF2_TYPE_UINT64:
            return QVariant(QString(value).toULongLong());

        case KCONF2_TYPE_DOUBLE:
            return QVariant(QString(value).toDouble());

        case KCONF2_TYPE_STRING:
            return QVariant(QString(value));

        case KCONF2_TYPE_ARRAY:
        default:
            return QVariant();
        }
}

QVariant KConf2Private::stringListToQvariant(char **strList) const
{
    QStringList list;
    for (int i = 0; strList[i]; i++)
        list.append((strList[i]));
    return QVariant(list);
}

char **KConf2Private::strvFromQVariant(const QVariant &variant)
{
    QStringList strList = variant.toStringList();
    if(strList.isEmpty())
        return nullptr;
    char** result = new char*[strList.count()+1];
    for (int i = 0; i < strList.count(); ++i) {
        QByteArray byteArray = strList.at(i).toUtf8();
        const int size = byteArray.size() + 1;
        result[i] = new char[size];
        strncpy(result[i], byteArray.constData(), size); 
        result[i][size - 1] = '\0'; 
    }
    result[strList.count()] = nullptr;
    return result;
}

}

#include "kconf2.moc"
#include "moc_kconf2.cpp"
