/*
 * optionsparser.cpp - plugin
 * Copyright (C) 2010  Evgeny Khryukin
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

#include "optionsparser.h"
#include <QKeySequence>
#include <QRect>
#include <QSize>
#include <QStringList>

OptionsParser::OptionsParser(QObject *parent) : QObject(parent) { }

OptionsParser *OptionsParser::instance()
{
    if (!instance_)
        instance_ = new OptionsParser();

    return instance_;
}

// stolen from varianttree.cpp

void OptionsParser::variantToElement(const QVariant &var, QDomElement &e)
{
    QString type = var.typeName();
    if (type == "QVariantList") {
        const auto varList = var.toList();
        for (const auto &v : varList) {
            QDomElement item_element = e.ownerDocument().createElement("item");
            variantToElement(v, item_element);
            e.appendChild(item_element);
        }
    } else if (type == "QStringList") {
        const auto varStrList = var.toStringList();
        for (const auto &s : varStrList) {
            QDomElement item_element = e.ownerDocument().createElement("item");
            QDomText    text         = e.ownerDocument().createTextNode(s);
            item_element.appendChild(text);
            e.appendChild(item_element);
        }
    } else if (type == "QSize") {
        QSize       size          = var.toSize();
        QDomElement width_element = e.ownerDocument().createElement("width");
        width_element.appendChild(e.ownerDocument().createTextNode(QString::number(size.width())));
        e.appendChild(width_element);
        QDomElement height_element = e.ownerDocument().createElement("height");
        height_element.appendChild(e.ownerDocument().createTextNode(QString::number(size.height())));
        e.appendChild(height_element);
    } else if (type == "QRect") {
        QRect       rect      = var.toRect();
        QDomElement x_element = e.ownerDocument().createElement("x");
        x_element.appendChild(e.ownerDocument().createTextNode(QString::number(rect.x())));
        e.appendChild(x_element);
        QDomElement y_element = e.ownerDocument().createElement("y");
        y_element.appendChild(e.ownerDocument().createTextNode(QString::number(rect.y())));
        e.appendChild(y_element);
        QDomElement width_element = e.ownerDocument().createElement("width");
        width_element.appendChild(e.ownerDocument().createTextNode(QString::number(rect.width())));
        e.appendChild(width_element);
        QDomElement height_element = e.ownerDocument().createElement("height");
        height_element.appendChild(e.ownerDocument().createTextNode(QString::number(rect.height())));
        e.appendChild(height_element);
    } else if (type == "QByteArray") {
        QDomText text = e.ownerDocument().createTextNode(Base64::encode(var.toByteArray()));
        e.appendChild(text);
    } else if (type == "QKeySequence") {
        QKeySequence k    = var.value<QKeySequence>();
        QDomText     text = e.ownerDocument().createTextNode(k.toString());
        e.appendChild(text);
    } else {
        QDomText text = e.ownerDocument().createTextNode(var.toString());
        e.appendChild(text);
    }
    e.setAttribute("type", type);
}

QVariant OptionsParser::elementToVariant(const QDomElement &e)
{
    QVariant value;
    QString  type = e.attribute("type");
    if (type == "QStringList") {
        QStringList list;
        for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
            QDomElement e = node.toElement();
            if (!e.isNull() && e.tagName() == "item") {
                list += e.text();
            }
        }
        value = list;
    } else if (type == "QVariantList") {
        QVariantList list;
        for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
            QDomElement e = node.toElement();
            if (!e.isNull() && e.tagName() == "item") {
                QVariant v = elementToVariant(e);
                if (v.isValid())
                    list.append(v);
            }
        }
        value = list;
    } else if (type == "QSize") {
        int width = 0, height = 0;
        for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
            QDomElement e = node.toElement();
            if (!e.isNull()) {
                if (e.tagName() == "width") {
                    width = e.text().toInt();
                } else if (e.tagName() == "height") {
                    height = e.text().toInt();
                }
            }
        }
        value = QVariant(QSize(width, height));
    } else if (type == "QRect") {
        int x = 0, y = 0, width = 0, height = 0;
        for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
            QDomElement e = node.toElement();
            if (!e.isNull()) {
                if (e.tagName() == "width") {
                    width = e.text().toInt();
                } else if (e.tagName() == "height") {
                    height = e.text().toInt();
                } else if (e.tagName() == "x") {
                    x = e.text().toInt();
                } else if (e.tagName() == "y") {
                    y = e.text().toInt();
                }
            }
        }
        value = QVariant(QRect(x, y, width, height));
    } else if (type == "QByteArray") {
        value = QByteArray();
        for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
            if (node.isText()) {
                value = Base64::decode(node.toText().data());
                break;
            }
        }
    } else { // Standard values
        QVariant::Type varianttype;
        bool           known = true;

        if (type == "QString") {
            varianttype = QVariant::String;
        } else if (type == "bool") {
            varianttype = QVariant::Bool;
        } else if (type == "int") {
            varianttype = QVariant::Int;
        } else if (type == "QKeySequence") {
            varianttype = QVariant::KeySequence;
        } else if (type == "QColor") {
            varianttype = QVariant::Color;
        } else {
            known = false;
        }

        if (known) {
            for (QDomNode node = e.firstChild(); !node.isNull(); node = node.nextSibling()) {
                if (node.isText())
                    value = node.toText().data();
            }

            if (!value.isValid())
                value = QString("");

            value.convert(varianttype);
        } else {
            value = QVariant();
        }
    }
    return value;
}

OptionsParser *OptionsParser::instance_ = nullptr;

//-----------------------
//---------Base64--------
//-----------------------
QString Base64::encode(const QByteArray &s)
{
    int  i;
    int  len   = s.size();
    char tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
    int  a, b, c;

    QByteArray p;
    p.resize((len + 2) / 3 * 4);
    int at = 0;
    for (i = 0; i < len; i += 3) {
        a = ((unsigned char)s[i] & 3) << 4;
        if (i + 1 < len) {
            a += (unsigned char)s[i + 1] >> 4;
            b = ((unsigned char)s[i + 1] & 0xF) << 2;
            if (i + 2 < len) {
                b += (unsigned char)s[i + 2] >> 6;
                c = (unsigned char)s[i + 2] & 0x3F;
            } else
                c = 64;
        } else {
            b = c = 64;
        }

        p[at++] = tbl[(unsigned char)s[i] >> 2];
        p[at++] = tbl[a];
        p[at++] = tbl[b];
        p[at++] = tbl[c];
    }
    return QString::fromLatin1(p);
}

QByteArray Base64::decode(const QString &input)
{
    QByteArray s(QString(input).remove('\n').toUtf8());
    QByteArray p;

    // -1 specifies invalid
    // 64 specifies eof
    // everything else specifies data

    signed char tbl[] = {
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, 52, 53, 54, 55,
        56, 57, 58, 59, 60, 61, -1, -1, -1, 64, -1, -1, -1, 0,  1,  2,  3,  4,  5,  6,  7,  8,  9,  10, 11, 12,
        13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, -1, 26, 27, 28, 29, 30, 31, 32,
        33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
    };

    // this should be a multiple of 4
    int len = s.size();

    if (len % 4) {
        return p;
    }

    p.resize(len / 4 * 3);

    int i;
    int at = 0;

    int a, b, c, d;
    c = d = 0;

    for (i = 0; i < len; i += 4) {
        a = tbl[(int)s[i]];
        b = tbl[(int)s[i + 1]];
        c = tbl[(int)s[i + 2]];
        d = tbl[(int)s[i + 3]];
        if ((a == 64 || b == 64) || (a < 0 || b < 0 || c < 0 || d < 0)) {
            p.resize(0);
            return p;
        }
        p[at++] = ((a & 0x3F) << 2) | ((b >> 4) & 0x03);
        p[at++] = ((b & 0x0F) << 4) | ((c >> 2) & 0x0F);
        p[at++] = ((c & 0x03) << 6) | ((d >> 0) & 0x3F);
    }

    if (c & 64)
        p.resize(at - 2);
    else if (d & 64)
        p.resize(at - 1);

    return p;
}
