/*****************************************************************************
 * cssedit.cpp - QStarDict, a StarDict clone written with using Qt           *
 * Copyright (C) 2008-2019 Alexander Rodin                                   *
 *                                                                           *
 * 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, write to the Free Software Foundation, Inc.,   *
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.               *
 *****************************************************************************/

#include "cssedit.h"

#include <QApplication>
#include <QColorDialog>

namespace QStarDict
{
CSSEdit::CSSEdit(QWidget *parent)
    : QWidget(parent)
{
    setupUi(this);

    connect(m_elementCombo, SIGNAL(currentIndexChanged(int)), SLOT(setCurrentElement(int)));
    connect(m_fontCombo, SIGNAL(currentFontChanged(const QFont&)), SLOT(propertyChanged()));
    connect(m_sizeSpin, SIGNAL(valueChanged(int)), SLOT(propertyChanged()));
    connect(m_boldButton, SIGNAL(toggled(bool)), SLOT(propertyChanged()));
    connect(m_italicButton, SIGNAL(toggled(bool)), SLOT(propertyChanged()));
    connect(m_underlineButton, SIGNAL(toggled(bool)), SLOT(propertyChanged()));
    connect(m_colorButton, SIGNAL(clicked()), SLOT(colorSelectClicked()));
    connect(m_backgroundButton, SIGNAL(clicked()), SLOT(colorSelectClicked()));
}

void CSSEdit::setCSS(const QString &css)
{
    m_elements.clear();
    for (auto &elementName: m_elementsNames)
        m_elements[elementName] = Element();
    bool inBlock = false;
    QString element;
    QString currentProperty;
    QString currentValue;
    for (int i = 0; i < css.length(); ++i)
    {
        if (! inBlock)
        {
            element.clear();
            for (; i < css.length() && css[i] != '{'; ++i)
                if (! css[i].isSpace())
                    element += css[i];
            if (i < css.length() && css[i] == '{')
            {
                inBlock = true;
                ++i;
            }
        }
        else
        {
            currentProperty.clear();
            for (; i < css.length() && css[i] != ':'; ++i)
            {
                if (css[i] == '}')
                {
                    inBlock = false;
                    break;
                }
                if (! css[i].isSpace())
                    currentProperty += css[i];
            }
            if (! inBlock)
                continue;
            currentValue.clear();
            char quote = '\0';
            for (++i; i < css.length() && css[i] != ';'; ++i)
            {
                if (quote)
                {
                    if (css[i] == quote)
                    {
                        while (i < css.length() && css[i] != ';')
                            ++i;
                        break;
                    }
                    else
                        currentValue += css[i];
                }
                else if (! css[i].isSpace())
                {
                    if (css[i] == '\'' || css[i] == '\"')
                        quote = css[i].toLatin1();
                    else
                        currentValue += css[i];
                }
            }
            m_elements[element][currentProperty] = currentValue;
            while (css[i + 1].isSpace())
                ++i;
            if (css[i + 1] == '}')
            {
                ++i;
                inBlock = false;
            }
        }
    }

    updateElementCombo();
    updatePreview();
    setCurrentElement(m_elementCombo->currentIndex());
}

void CSSEdit::setElementsNames(const QVector<QPair<QString, QString>> &aliases)
{
    m_elementsNames.clear();
    m_elementsAliases.clear();
    for (auto &i: aliases)
    {
        m_elementsNames.append(i.first);
        m_elementsAliases[i.first] = i.second;
    }
    updateElementCombo();
    updatePreview();
}

QString CSSEdit::css() const
{
    QString result;
    for (auto &elementName: m_elementsNames)
    {
        auto &element = m_elements[elementName];
        result += elementName + "\n{\n";
        for (Element::const_iterator j = element.begin(); j != element.end(); ++j)
        {
            result += j.key() + ": ";
            if (j->contains(' '))
                result += "\"" + *j + "\";\n";
            else
                result += *j + ";\n";
        }
        result += "}\n";
    }
    return result;
}

void CSSEdit::setCurrentElement(int index)
{
    if (! m_elements.contains(m_elementCombo->itemData(index).toString()))
        return;
    m_currentElement = m_elementCombo->itemData(index).toString();
    Element *element = &m_elements[m_currentElement];
    Element parentElement = getParentElement(m_currentElement);

    QColor color(element->contains("color") ? element->value("color") : parentElement["color"]);
    m_colorButton->setText(color.name());
    QPalette palette = m_colorButton->palette();
    palette.setColor(QPalette::Normal, QPalette::ButtonText, color);
    m_colorButton->setPalette(palette);

    color = QColor(element->contains("background-color") ?
        element->value("background-color") : parentElement["background-color"]);
    m_backgroundButton->setText(color.name());
    palette = m_backgroundButton->palette();
    palette.setColor(QPalette::Normal, QPalette::ButtonText, color);
    m_backgroundButton->setPalette(palette);

    m_fontCombo->setCurrentFont(element->contains("font-family") ?
            element->value("font-family") : parentElement["font-family"]);

    QString value = element->contains("font-size") ?
        element->value("font-size") : parentElement["font-size"];
    if (! value.endsWith("pt"))
        value = parentElement["font-size"];
    m_sizeSpin->setValue(value.left(value.length() - 2).toInt());

    value = element->contains("font-weight") ? element->value("font-weight") : parentElement["font-weight"];
    m_boldButton->setChecked(value == "bold");

    value = element->contains("font-style") ? element->value("font-style") : parentElement["font-style"];
    m_italicButton->setChecked(value == "italic");

    value = element->contains("text-decoration") ? element->value("text-decoration") : parentElement["text-decoration"];
    m_underlineButton->setChecked(value == "underline");

    updatePreview();
}

void CSSEdit::propertyChanged()
{
    if (! sender())
        return;
    if (! m_elements.contains(m_currentElement))
        return;
    Element *element = &m_elements[m_currentElement];
    Element parentElement = getParentElement(m_currentElement);

    if (sender() == m_fontCombo)
    {
        QString font = m_fontCombo->currentText();
        if (parentElement["font-family"] == font)
            element->remove("font-family");
        else
            element->insert("font-family", font);
    }
    else if (sender() == m_sizeSpin)
    {
        QString size = QString::number(m_sizeSpin->value()) + "pt";
        if (parentElement["font-size"] == size)
            element->remove("font-size");
        else
            element->insert("font-size", size);
    }
    else if (sender() == m_boldButton)
    {
        QString weight = (m_boldButton->isChecked() ? "bold" : "normal");
        if (parentElement["font-weight"] == weight)
            element->remove("font-weight");
        else
            element->insert("font-weight", weight);
    }
    else if(sender() == m_italicButton)
    {
        QString style = (m_italicButton->isChecked() ? "italic" : "normal");
        if (parentElement["font-style"] == style)
            element->remove("font-style");
        else
            element->insert("font-style", style);
    }
    else if(sender() == m_underlineButton)
    {
        QString decoration = (m_underlineButton->isChecked() ? "underline" : "none");
        if (parentElement["text-decoration"] == decoration)
            element->remove("text-decoration");
        else
            element->insert("text-decoration", decoration);
    }

    updatePreview();
}

void CSSEdit::colorSelectClicked()
{
    QToolButton *colorButton;
    QString propertyName;
    if (sender() == m_colorButton)
    {
        colorButton = m_colorButton;
        propertyName = "color";
    }
    else if (sender() == m_backgroundButton)
    {
        colorButton = m_backgroundButton;
        propertyName = "background-color";
    }
    else
        return;

    if (! m_elements.contains(m_currentElement))
        return;
    Element *element = &m_elements[m_currentElement];
    Element parentElement = getParentElement(m_currentElement);
    QColor color = QColorDialog::getColor(QColor(colorButton->text()), this);
    if (color.isValid())
    {
        colorButton->setText(color.name());
        QPalette palette = colorButton->palette();
        palette.setColor(QPalette::Normal, QPalette::ButtonText, color);
        colorButton->setPalette(palette);
        if (parentElement[propertyName] == color.name())
            element->remove(propertyName);
        else
            element->insert(propertyName, color.name());
        updatePreview();
    }
}

void CSSEdit::updatePreview()
{
    QString html = "<style>" +  css()  +  "</style>";
    html += "<body>";
    for (auto &elementName: m_elementsNames)
    {
        QString alias;
        if (m_elementsAliases.contains(elementName))
            alias = m_elementsAliases.value(elementName);
        else
            alias = elementName;
        int pos = elementName.indexOf('.');
        if (pos == -1)
            html += "<" + elementName + ">" + alias + "</" + elementName + "><br>";
        else
        {
            QString parent = elementName.left(pos);
            QString class_ = elementName.mid(pos + 1);
            html += "<" + parent + " class=\'" + class_ + "\'>" + alias + "</" + parent + "><br>";
        }
    }
    html += "</body>";
    m_preview->setHtml(html);
}

void CSSEdit::updateElementCombo()
{
    m_elementCombo->clear();
    for (auto &elementName: m_elementsNames)
    {
        QString alias;
        if (m_elementsAliases.contains(elementName))
            alias = m_elementsAliases.value(elementName);
        else
            alias = elementName;
        m_elementCombo->addItem(alias, elementName);
    }
    if (m_elements.begin() != m_elements.end())
        m_currentElement = m_elements.begin().key();
    else
        m_currentElement.clear();
}

CSSEdit::Element CSSEdit::getParentElement(const QString &elementName)
{
    Element body = m_elements.value("body");

    if (elementName == "body")
    {
        body["color"] = QApplication::palette().color(QPalette::Normal, QPalette::Text).name();
        body["background-color"] = QApplication::palette().color(QPalette::Normal, QPalette::Base).name();
        body["font-family"] = QApplication::font().family();
        body["font-size"] = QString::number(QApplication::font().pointSize()) + "pt";
        body["font-weight"] = (QApplication::font().weight() == QFont::Bold) ? "bold" : "normal";
        body["font-style"] = (QApplication::font().style() == QFont::StyleItalic) ? "italic" : "normal";
        body["text-decoration"] = QApplication::font().underline() ? "underline" : "none";
        return body;
    }
    else
    {
        if (! body.contains("color"))
            body["color"] = QApplication::palette().color(QPalette::Normal, QPalette::Text).name();
        if (! body.contains("background-color"))
            body["background-color"] = QApplication::palette().color(QPalette::Normal, QPalette::Base).name();
        if (! body.contains("font-family"))
            body["font-family"] = QApplication::font().family();
        if (! body.contains("font-size"))
            body["font-size"] = QString::number(QApplication::font().pointSize()) + "pt";
        if (! body.contains("font-weight"))
            body["font-weight"] = (QApplication::font().weight() == QFont::Bold) ? "bold" : "normal";
        if (! body.contains("font-style"))
            body["font-style"] = (QApplication::font().style() == QFont::StyleItalic) ? "italic" : "normal";
        if (! body.contains("text-decoration"))
            body["text-decoration"] = QApplication::font().underline() ? "underline" : "none";
    }

    QString parentName;
    int pos = elementName.indexOf('.');
    if (pos != -1)
    {
        parentName = elementName.left(pos);
        if (m_elements.contains(parentName))
        {
            Element parent = m_elements[parentName];
            for (Element::const_iterator i = body.begin(); i != body.end(); ++i)
                if (! parent.contains(i.key()))
                    parent[i.key()] = *i;
            return parent;
        }
        else
            return body;
    }
    return body;
}
}

// vim: tabstop=4 softtabstop=4 shiftwidth=4 expandtab cindent textwidth=120 formatoptions=tc

