/*****************************************************************************
 * grayscale.cpp - QStarDict, a dictionary for learning foreign languages    *
 * Copyright (C) 2023 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 "grayscale.h"

#include <QColor>
#include <cctype>
#include <set>
#include <string>
#include <vector>
#include "htmlparser/html.hpp"

namespace
{

std::string trim(const std::string &string)
{
    return QString(string.c_str()).trimmed().toUtf8().data();
}

std::string lowercase(std::string string)
{
    for (char &c: string)
        c = tolower(c);
    return string;
}

std::string toGrayscale(const std::string &colorName)
{
    QColor color(trim(colorName).c_str());
    return "#" + QString::number(qGray(color.rgb()), 16).repeated(3).toStdString();
}

std::string toGrayscaleComplex(const std::string &complexStyle)
{
    // complex style is a style in the form "1px solid #ff0000"
    int index = static_cast<int>(complexStyle.length()) - 1;
    while (index >= 0 && ! isspace(complexStyle[index]))
        index--;
    std::string prefix = complexStyle.substr(0, index + 1); // "1px solid " part
    std::string color = complexStyle.substr(index + 1, complexStyle.length()); // "#ff0000" part
    return prefix + toGrayscale(color);
}

void transformCSSValue(std::string property, std::string &value)
{
    property = lowercase(trim(property));

    const static std::set<std::string> colorProperties = {
        "color",
        "background-color",
        "border-color",
        "border-top-color",
        "border-bottom-color",
        "border-left-color",
        "border-right-color"
    };
    if (colorProperties.contains(property))
        value = toGrayscale(value);

    const static std::set<std::string> complexProperties = {
        "border-top",
        "border-bottom",
        "border-left",
        "border-right"
    };
    if (complexProperties.contains(property))
        value = toGrayscaleComplex(value);
}

}

QString htmlToGrayscale(const QString &inputHtml)
{
    html::parser parser;
    html::node_ptr root = parser.parse(inputHtml.toUtf8().data());

    root->walk([](html::node &node) -> bool {
        // replace colors in the attributes of HTML tags
        const static std::vector<std::string> colorAttrs = {
            "color",
            "bgcolor"
        };
        for (const auto &attr: colorAttrs)
        {
            std::string color = node.get_attr(attr);
            if (! color.empty())
            {
                color = toGrayscale(color);
                node.set_attr(attr, color);
            }
        }
        
        // replace colors in CSS attributes
        std::string srcStyle = node.get_attr("style");
        std::string style;

        std::string property;
        std::string value;
        bool insideProperty = true;
        for (char c: node.get_attr("style"))
        {
            switch (c)
            {
                case ':':
                    insideProperty = false;
                    break;
                case ';':
                    transformCSSValue(property, value);
                    style += property + ":" + value + ";";

                    insideProperty = true;
                    property.clear();
                    value.clear();
                    break;
                default:
                    if (insideProperty)
                        property += c;
                    else
                        value += c;
            }
        }
        if (! insideProperty)
        {
            transformCSSValue(property, value);
            style += property + ":" + value;
        }
        if (! style.empty())
            node.set_attr("style", style);

        return true;
    });

    return QString::fromUtf8(root->to_html().c_str());
}
