#include "parser.h"
#include "parserutils.h"
#include "layoutcontext.h"

#include "clippathelement.h"
#include "defselement.h"
#include "gelement.h"
#include "geometryelement.h"
#include "markerelement.h"
#include "maskelement.h"
#include "paintelement.h"
#include "stopelement.h"
#include "svgelement.h"
#include "symbolelement.h"
#include "useelement.h"
#include "styleelement.h"

namespace lunasvg {

Length Parser::parseLength(const std::string& string, LengthNegativeValuesMode mode, const Length& defaultValue)
{
    if(string.empty())
        return defaultValue;

    auto ptr = string.data();
    auto end = ptr + string.size();

    double value;
    LengthUnits units;
    if(!parseLength(ptr, end, value, units, mode))
        return defaultValue;

    return Length{value, units};
}

LengthList Parser::parseLengthList(const std::string& string, LengthNegativeValuesMode mode)
{
    if(string.empty())
        return LengthList{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    double value;
    LengthUnits units;

    LengthList values;
    while(ptr < end) {
        if(!parseLength(ptr, end, value, units, mode))
            break;
        values.emplace_back(value, units);
        Utils::skipWsComma(ptr, end);
    }

    return values;
}

double Parser::parseNumber(const std::string& string, double defaultValue)
{
    if(string.empty())
        return defaultValue;

    auto ptr = string.data();
    auto end = ptr + string.size();

    double value;
    if(!Utils::parseNumber(ptr, end, value))
        return defaultValue;

    return value;
}

double Parser::parseNumberPercentage(const std::string& string, double defaultValue)
{
    if(string.empty())
        return defaultValue;

    auto ptr = string.data();
    auto end = ptr + string.size();

    double value;
    if(!Utils::parseNumber(ptr, end, value))
        return defaultValue;

    if(Utils::skipDesc(ptr, end, '%'))
        value /= 100.0;
    return value < 0.0 ? 0.0 : value > 1.0 ? 1.0 : value;
}

PointList Parser::parsePointList(const std::string& string)
{
    if(string.empty())
        return PointList{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    double x;
    double y;

    PointList values;
    while(ptr < end) {
        if(!Utils::parseNumber(ptr, end, x)
            || !Utils::skipWsComma(ptr, end)
            || !Utils::parseNumber(ptr, end, y)) {
            break;
        }

        values.emplace_back(x, y);
        Utils::skipWsComma(ptr, end);
    }

    return values;
}

Transform Parser::parseTransform(const std::string& string)
{
    if(string.empty())
        return Transform{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    TransformType type;
    double values[6];
    int count;

    Transform transform;
    while(ptr < end) {
        if(!parseTransform(ptr, end, type, values, count))
            break;
        Utils::skipWsComma(ptr, end);
        switch(type) {
        case TransformType::Matrix:
            transform.transform(values[0], values[1], values[2], values[3], values[4], values[5]);
            break;
        case TransformType::Rotate:
            if(count == 1)
                transform.rotate(values[0], 0, 0);
            else
                transform.rotate(values[0], values[1], values[2]);
            break;
        case TransformType::Scale:
            if(count == 1)
                transform.scale(values[0], values[0]);
            else
                transform.scale(values[0], values[1]);
            break;
        case TransformType::SkewX:
            transform.shear(values[0], 0);
            break;
        case TransformType::SkewY:
            transform.shear(0, values[0]);
            break;
        case TransformType::Translate:
            if(count == 1)
                transform.translate(values[0], 0);
            else
                transform.translate(values[0], values[1]);
            break;
        }
    }

    return transform;
}

Path Parser::parsePath(const std::string& string)
{
    auto ptr = string.data();
    auto end = ptr + string.size();
    if(ptr >= end || !(*ptr == 'M' || *ptr == 'm'))
        return Path{};

    auto command = *ptr++;
    auto lastCommand = command;
    double c[6];
    bool f[2];

    Point startPoint;
    Point currentPoint;
    Point controlPoint;

    Path path;
    while(true) {
        Utils::skipWs(ptr, end);
        if(command == 'M' || command == 'm') {
            if(!parseNumberList(ptr, end, c, 2))
                return path;

            if(command == 'm') {
                c[0] += currentPoint.x;
                c[1] += currentPoint.y;
            }

            path.moveTo(c[0], c[1]);
            startPoint.x = currentPoint.x = c[0];
            startPoint.y = currentPoint.y = c[1];
            command = command == 'm' ? 'l' : 'L';
        } else if(command == 'L' || command == 'l') {
            if(!parseNumberList(ptr, end, c, 2))
                return path;

            if(command == 'l') {
                c[0] += currentPoint.x;
                c[1] += currentPoint.y;
            }

            path.lineTo(c[0], c[1]);
            currentPoint.x = c[0];
            currentPoint.y = c[1];
        } else if(command == 'H' || command == 'h') {
            if(!parseNumberList(ptr, end, c, 1))
                return path;

            if(command == 'h')
               c[0] += currentPoint.x;

            path.lineTo(c[0], currentPoint.y);
            currentPoint.x = c[0];
        } else if(command == 'V' || command == 'v') {
            if(!parseNumberList(ptr, end, c + 1, 1))
                return path;

            if(command == 'v')
               c[1] += currentPoint.y;

            path.lineTo(currentPoint.x, c[1]);
            currentPoint.y = c[1];
        } else if(command == 'Q' || command == 'q') {
            if(!parseNumberList(ptr, end, c, 4))
                return path;

            if(command == 'q') {
                c[0] += currentPoint.x;
                c[1] += currentPoint.y;
                c[2] += currentPoint.x;
                c[3] += currentPoint.y;
            }

            path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]);
            controlPoint.x = c[0];
            controlPoint.y = c[1];
            currentPoint.x = c[2];
            currentPoint.y = c[3];
        } else if(command == 'C' || command == 'c') {
            if(!parseNumberList(ptr, end, c, 6))
                return path;

            if(command == 'c') {
                c[0] += currentPoint.x;
                c[1] += currentPoint.y;
                c[2] += currentPoint.x;
                c[3] += currentPoint.y;
                c[4] += currentPoint.x;
                c[5] += currentPoint.y;
            }

            path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]);
            controlPoint.x = c[2];
            controlPoint.y = c[3];
            currentPoint.x = c[4];
            currentPoint.y = c[5];
        } else if(command == 'T' || command == 't') {
            if(lastCommand != 'Q' && lastCommand != 'q' && lastCommand != 'T' && lastCommand != 't') {
                c[0] = currentPoint.x;
                c[1] = currentPoint.y;
            } else {
                c[0] = 2 * currentPoint.x - controlPoint.x;
                c[1] = 2 * currentPoint.y - controlPoint.y;
            }

            if(!parseNumberList(ptr, end, c + 2, 2))
                return path;

            if(command == 't') {
                c[2] += currentPoint.x;
                c[3] += currentPoint.y;
            }

            path.quadTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], c[3]);
            controlPoint.x = c[0];
            controlPoint.y = c[1];
            currentPoint.x = c[2];
            currentPoint.y = c[3];
        } else if(command == 'S' || command == 's') {
            if(lastCommand != 'C' && lastCommand != 'c' && lastCommand != 'S' && lastCommand != 's') {
                c[0] = currentPoint.x;
                c[1] = currentPoint.y;
            } else {
                c[0] = 2 * currentPoint.x - controlPoint.x;
                c[1] = 2 * currentPoint.y - controlPoint.y;
            }

            if(!parseNumberList(ptr, end, c + 2, 4))
                return path;

            if(command == 's') {
                c[2] += currentPoint.x;
                c[3] += currentPoint.y;
                c[4] += currentPoint.x;
                c[5] += currentPoint.y;
            }

            path.cubicTo(c[0], c[1], c[2], c[3], c[4], c[5]);
            controlPoint.x = c[2];
            controlPoint.y = c[3];
            currentPoint.x = c[4];
            currentPoint.y = c[5];
        } else if(command == 'A' || command == 'a') {
            if(!parseNumberList(ptr, end, c, 3)
                || !parseArcFlag(ptr, end, f[0])
                || !parseArcFlag(ptr, end, f[1])
                || !parseNumberList(ptr, end, c + 3, 2)) {
                return path;
            }

            if(command == 'a') {
               c[3] += currentPoint.x;
               c[4] += currentPoint.y;
            }

            path.arcTo(currentPoint.x, currentPoint.y, c[0], c[1], c[2], f[0], f[1], c[3], c[4]);
            currentPoint.x = c[3];
            currentPoint.y = c[4];
        } else if(command == 'Z' || command == 'z') {
            path.close();
            currentPoint.x = startPoint.x;
            currentPoint.y = startPoint.y;
        } else {
            return path;
        }

        Utils::skipWsComma(ptr, end);
        if(ptr >= end)
            break;

        lastCommand = command;
        if(IS_ALPHA(*ptr)) {
            command = *ptr++;
        }
    }

    return path;
}

std::string Parser::parseUrl(const std::string& string)
{
    if(string.empty())
        return std::string{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    std::string value;
    parseUrlFragment(ptr, end, value);
    return value;
}

std::string Parser::parseHref(const std::string& string)
{
    if(string.size() > 1 && string.front() == '#')
        return string.substr(1);
    return std::string{};
}

Rect Parser::parseViewBox(const std::string& string)
{
    if(string.empty())
        return Rect::Invalid;

    auto ptr = string.data();
    auto end = ptr + string.size();

    double x;
    double y;
    double w;
    double h;
    if(!Utils::parseNumber(ptr, end, x)
        || !Utils::skipWsComma(ptr, end)
        || !Utils::parseNumber(ptr, end, y)
        || !Utils::skipWsComma(ptr, end)
        || !Utils::parseNumber(ptr, end, w)
        || !Utils::skipWsComma(ptr, end)
        || !Utils::parseNumber(ptr, end, h)) {
        return Rect::Invalid;
    }

    if(w < 0.0 || h < 0.0)
        return Rect::Invalid;
    return Rect{x, y, w, h};
}

PreserveAspectRatio Parser::parsePreserveAspectRatio(const std::string& string)
{
    if(string.empty())
        return PreserveAspectRatio{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    Align align{Align::xMidYMid};
    MeetOrSlice scale{MeetOrSlice::Meet};
    if(Utils::skipDesc(ptr, end, "none"))
        align = Align::None;
    else if(Utils::skipDesc(ptr, end, "xMinYMin"))
        align = Align::xMinYMin;
    else if(Utils::skipDesc(ptr, end, "xMidYMin"))
        align = Align::xMidYMin;
    else if(Utils::skipDesc(ptr, end, "xMaxYMin"))
        align = Align::xMaxYMin;
    else if(Utils::skipDesc(ptr, end, "xMinYMid"))
        align = Align::xMinYMid;
    else if(Utils::skipDesc(ptr, end, "xMidYMid"))
        align = Align::xMidYMid;
    else if(Utils::skipDesc(ptr, end, "xMaxYMid"))
        align = Align::xMaxYMid;
    else if(Utils::skipDesc(ptr, end, "xMinYMax"))
        align = Align::xMinYMax;
    else if(Utils::skipDesc(ptr, end, "xMidYMax"))
        align = Align::xMidYMax;
    else if(Utils::skipDesc(ptr, end, "xMaxYMax"))
        align = Align::xMaxYMax;
    else
        return PreserveAspectRatio{};

    Utils::skipWs(ptr, end);
    if(Utils::skipDesc(ptr, end, "slice"))
        scale = MeetOrSlice::Slice;
    else
        scale = MeetOrSlice::Meet;

    return PreserveAspectRatio{align, scale};
}

static const double pi = 3.14159265358979323846;

Angle Parser::parseAngle(const std::string& string)
{
    if(string.empty())
        return Angle{};

    auto ptr = string.data();
    auto end = ptr + string.size();

    if(Utils::skipDesc(ptr, end, "auto"))
        return MarkerOrient::Auto;

    double value;
    if(!Utils::parseNumber(ptr, end, value))
        return Angle{};

    if(Utils::skipDesc(ptr, end, "rad"))
        value *= 180.0 / pi;
    else if(Utils::skipDesc(ptr, end, "grad"))
        value *= 360.0 / 400.0;

    return Angle{value, MarkerOrient::Angle};
}

MarkerUnits Parser::parseMarkerUnits(const std::string& string)
{
    if(string.empty())
        return MarkerUnits::StrokeWidth;

    if(string.compare("userSpaceOnUse") == 0)
        return MarkerUnits::UserSpaceOnUse;
    return MarkerUnits::StrokeWidth;
}

SpreadMethod Parser::parseSpreadMethod(const std::string& string)
{
    if(string.empty())
        return SpreadMethod::Pad;

    if(string.compare("repeat") == 0)
        return SpreadMethod::Repeat;
    if(string.compare("reflect") == 0)
        return SpreadMethod::Reflect;
    return SpreadMethod::Pad;
}

Units Parser::parseUnits(const std::string& string, Units defaultValue)
{
    if(string.empty())
        return defaultValue;

    if(string.compare("userSpaceOnUse") == 0)
        return Units::UserSpaceOnUse;
    if(string.compare("objectBoundingBox") == 0)
        return Units::ObjectBoundingBox;
    return defaultValue;
}

Color Parser::parseColor(const std::string& string, const StyledElement* element, const Color& defaultValue)
{
    if(string.empty())
        return defaultValue;

    auto ptr = string.data();
    auto end = ptr + string.size();

    if(Utils::skipDesc(ptr, end, '#')) {
        auto start = ptr;
        unsigned int value;
        if(!Utils::parseInteger(ptr, end, value, 16))
            return defaultValue;

        auto n = ptr - start;
        if(n != 3 && n != 6)
            return defaultValue;

        if(n == 3) {
            value = ((value&0xf00) << 8) | ((value&0x0f0) << 4) | (value&0x00f);
            value |= value << 4;
        }

        return Color(value | 0xFF000000);
    }

    if(Utils::skipDesc(ptr, end, "rgb(")) {
        int r, g, b;
        if(!Utils::skipWs(ptr, end)
            || !parseColorComponent(ptr, end, r)
            || !Utils::skipWsComma(ptr, end)
            || !parseColorComponent(ptr, end, g)
            || !Utils::skipWsComma(ptr, end)
            || !parseColorComponent(ptr, end, b)
            || !Utils::skipWs(ptr, end)
            || !Utils::skipDesc(ptr, end, ')')) {
            return defaultValue;
        }

        return Color(r, g, b, 255);
    }

    if(Utils::skipDesc(ptr, end, "none"))
        return Color::Transparent;

    if(Utils::skipDesc(ptr, end, "currentColor"))
        return element->color();

    static const std::map<std::string, unsigned int> colormap = {
        {"aliceblue", 0xF0F8FF},
        {"antiquewhite", 0xFAEBD7},
        {"aqua", 0x00FFFF},
        {"aquamarine", 0x7FFFD4},
        {"azure", 0xF0FFFF},
        {"beige", 0xF5F5DC},
        {"bisque", 0xFFE4C4},
        {"black", 0x000000},
        {"blanchedalmond", 0xFFEBCD},
        {"blue", 0x0000FF},
        {"blueviolet", 0x8A2BE2},
        {"brown", 0xA52A2A},
        {"burlywood", 0xDEB887},
        {"cadetblue", 0x5F9EA0},
        {"chartreuse", 0x7FFF00},
        {"chocolate", 0xD2691E},
        {"coral", 0xFF7F50},
        {"cornflowerblue", 0x6495ED},
        {"cornsilk", 0xFFF8DC},
        {"crimson", 0xDC143C},
        {"cyan", 0x00FFFF},
        {"darkblue", 0x00008B},
        {"darkcyan", 0x008B8B},
        {"darkgoldenrod", 0xB8860B},
        {"darkgray", 0xA9A9A9},
        {"darkgreen", 0x006400},
        {"darkgrey", 0xA9A9A9},
        {"darkkhaki", 0xBDB76B},
        {"darkmagenta", 0x8B008B},
        {"darkolivegreen", 0x556B2F},
        {"darkorange", 0xFF8C00},
        {"darkorchid", 0x9932CC},
        {"darkred", 0x8B0000},
        {"darksalmon", 0xE9967A},
        {"darkseagreen", 0x8FBC8F},
        {"darkslateblue", 0x483D8B},
        {"darkslategray", 0x2F4F4F},
        {"darkslategrey", 0x2F4F4F},
        {"darkturquoise", 0x00CED1},
        {"darkviolet", 0x9400D3},
        {"deeppink", 0xFF1493},
        {"deepskyblue", 0x00BFFF},
        {"dimgray", 0x696969},
        {"dimgrey", 0x696969},
        {"dodgerblue", 0x1E90FF},
        {"firebrick", 0xB22222},
        {"floralwhite", 0xFFFAF0},
        {"forestgreen", 0x228B22},
        {"fuchsia", 0xFF00FF},
        {"gainsboro", 0xDCDCDC},
        {"ghostwhite", 0xF8F8FF},
        {"gold", 0xFFD700},
        {"goldenrod", 0xDAA520},
        {"gray", 0x808080},
        {"green", 0x008000},
        {"greenyellow", 0xADFF2F},
        {"grey", 0x808080},
        {"honeydew", 0xF0FFF0},
        {"hotpink", 0xFF69B4},
        {"indianred", 0xCD5C5C},
        {"indigo", 0x4B0082},
        {"ivory", 0xFFFFF0},
        {"khaki", 0xF0E68C},
        {"lavender", 0xE6E6FA},
        {"lavenderblush", 0xFFF0F5},
        {"lawngreen", 0x7CFC00},
        {"lemonchiffon", 0xFFFACD},
        {"lightblue", 0xADD8E6},
        {"lightcoral", 0xF08080},
        {"lightcyan", 0xE0FFFF},
        {"lightgoldenrodyellow", 0xFAFAD2},
        {"lightgray", 0xD3D3D3},
        {"lightgreen", 0x90EE90},
        {"lightgrey", 0xD3D3D3},
        {"lightpink", 0xFFB6C1},
        {"lightsalmon", 0xFFA07A},
        {"lightseagreen", 0x20B2AA},
        {"lightskyblue", 0x87CEFA},
        {"lightslategray", 0x778899},
        {"lightslategrey", 0x778899},
        {"lightsteelblue", 0xB0C4DE},
        {"lightyellow", 0xFFFFE0},
        {"lime", 0x00FF00},
        {"limegreen", 0x32CD32},
        {"linen", 0xFAF0E6},
        {"magenta", 0xFF00FF},
        {"maroon", 0x800000},
        {"mediumaquamarine", 0x66CDAA},
        {"mediumblue", 0x0000CD},
        {"mediumorchid", 0xBA55D3},
        {"mediumpurple", 0x9370DB},
        {"mediumseagreen", 0x3CB371},
        {"mediumslateblue", 0x7B68EE},
        {"mediumspringgreen", 0x00FA9A},
        {"mediumturquoise", 0x48D1CC},
        {"mediumvioletred", 0xC71585},
        {"midnightblue", 0x191970},
        {"mintcream", 0xF5FFFA},
        {"mistyrose", 0xFFE4E1},
        {"moccasin", 0xFFE4B5},
        {"navajowhite", 0xFFDEAD},
        {"navy", 0x000080},
        {"oldlace", 0xFDF5E6},
        {"olive", 0x808000},
        {"olivedrab", 0x6B8E23},
        {"orange", 0xFFA500},
        {"orangered", 0xFF4500},
        {"orchid", 0xDA70D6},
        {"palegoldenrod", 0xEEE8AA},
        {"palegreen", 0x98FB98},
        {"paleturquoise", 0xAFEEEE},
        {"palevioletred", 0xDB7093},
        {"papayawhip", 0xFFEFD5},
        {"peachpuff", 0xFFDAB9},
        {"peru", 0xCD853F},
        {"pink", 0xFFC0CB},
        {"plum", 0xDDA0DD},
        {"powderblue", 0xB0E0E6},
        {"purple", 0x800080},
        {"rebeccapurple", 0x663399},
        {"red", 0xFF0000},
        {"rosybrown", 0xBC8F8F},
        {"royalblue", 0x4169E1},
        {"saddlebrown", 0x8B4513},
        {"salmon", 0xFA8072},
        {"sandybrown", 0xF4A460},
        {"seagreen", 0x2E8B57},
        {"seashell", 0xFFF5EE},
        {"sienna", 0xA0522D},
        {"silver", 0xC0C0C0},
        {"skyblue", 0x87CEEB},
        {"slateblue", 0x6A5ACD},
        {"slategray", 0x708090},
        {"slategrey", 0x708090},
        {"snow", 0xFFFAFA},
        {"springgreen", 0x00FF7F},
        {"steelblue", 0x4682B4},
        {"tan", 0xD2B48C},
        {"teal", 0x008080},
        {"thistle", 0xD8BFD8},
        {"tomato", 0xFF6347},
        {"turquoise", 0x40E0D0},
        {"violet", 0xEE82EE},
        {"wheat", 0xF5DEB3},
        {"white", 0xFFFFFF},
        {"whitesmoke", 0xF5F5F5},
        {"yellow", 0xFFFF00},
        {"yellowgreen", 0x9ACD32}
    };

    auto it = colormap.find(string);
    if(it == colormap.end())
        return defaultValue;
    return Color(it->second | 0xFF000000);
}

Paint Parser::parsePaint(const std::string& string, const StyledElement* element, const Color& defaultValue)
{
    if(string.empty())
        return defaultValue;

    auto ptr = string.data();
    auto end = ptr + string.size();

    std::string ref;
    if(!parseUrlFragment(ptr, end, ref))
        return parseColor(string, element, defaultValue);

    std::string fallback{ptr, end};
    if(fallback.empty())
        return Paint{ref, Color::Transparent};
    return Paint{ref, parseColor(fallback, element, defaultValue)};
}

WindRule Parser::parseWindRule(const std::string& string)
{
    if(string.empty())
        return WindRule::NonZero;

    if(string.compare("evenodd") == 0)
        return WindRule::EvenOdd;
    return WindRule::NonZero;
}

LineCap Parser::parseLineCap(const std::string& string)
{
    if(string.empty())
        return LineCap::Butt;

    if(string.compare("round") == 0)
        return LineCap::Round;
    if(string.compare("square") == 0)
        return LineCap::Square;
    return LineCap::Butt;
}

LineJoin Parser::parseLineJoin(const std::string& string)
{
    if(string.empty())
        return LineJoin::Miter;

    if(string.compare("bevel") == 0)
        return LineJoin::Bevel;
    if(string.compare("round") == 0)
        return LineJoin::Round;
    return LineJoin::Miter;
}

Display Parser::parseDisplay(const std::string& string)
{
    if(string.empty())
        return Display::Inline;

    if(string.compare("none") == 0)
        return Display::None;
    return Display::Inline;
}

Visibility Parser::parseVisibility(const std::string& string)
{
    if(string.empty())
        return Visibility::Visible;

    if(string.compare("visible") == 0)
        return Visibility::Visible;
    return Visibility::Hidden;
}

Overflow Parser::parseOverflow(const std::string& string, Overflow defaultValue)
{
    if(string.empty())
        return defaultValue;

    if(string.compare("visible") == 0)
        return Overflow::Visible;
    if(string.compare("hidden") == 0)
        return Overflow::Hidden;
    return defaultValue;
}

bool Parser::parseLength(const char*& ptr, const char* end, double& value, LengthUnits& units, LengthNegativeValuesMode mode)
{
    if(!Utils::parseNumber(ptr, end, value))
        return false;

    if(mode == ForbidNegativeLengths && value < 0.0)
        return false;

    char c[2] = {0, 0};
    if(ptr + 0 < end) c[0] = ptr[0];
    if(ptr + 1 < end) c[1] = ptr[1];

    switch(c[0]) {
    case '%':
        units = LengthUnits::Percent;
        ptr += 1;
        break;
    case 'p':
        if(c[1] == 'x')
            units = LengthUnits::Px;
        else if(c[1] == 'c')
            units = LengthUnits::Pc;
        else if(ptr[1] == 't')
            units = LengthUnits::Pt;
        else
            return false;
        ptr += 2;
        break;
    case 'i':
        if(c[1] == 'n')
            units = LengthUnits::In;
        else
            return false;
        ptr += 2;
        break;
    case 'c':
        if(c[1] == 'm')
            units = LengthUnits::Cm;
        else
            return false;
        ptr += 2;
        break;
    case 'm':
        if(c[1] == 'm')
            units = LengthUnits::Mm;
        else
            return false;
        ptr += 2;
        break;
    case 'e':
        if(c[1] == 'm')
            units = LengthUnits::Em;
        else if(c[1] == 'x')
            units = LengthUnits::Ex;
        else
            return false;
        ptr += 2;
        break;
    default:
        units = LengthUnits::Number;
        break;
    }

    return true;
}

bool Parser::parseNumberList(const char*& ptr, const char* end, double* values, int count)
{
    for(int i = 0; i < count; i++) {
        if(!Utils::parseNumber(ptr, end, values[i]))
            return false;
        Utils::skipWsComma(ptr, end);
    }

    return true;
}

bool Parser::parseArcFlag(const char*& ptr, const char* end, bool& flag)
{
    if(ptr < end && *ptr == '0')
        flag = false;
    else if(ptr < end && *ptr == '1')
        flag = true;
    else
        return false;

    ++ptr;
    Utils::skipWsComma(ptr, end);
    return true;
}

bool Parser::parseColorComponent(const char*& ptr, const char* end, int& component)
{
    double value = 0;
    if(!Utils::parseNumber(ptr, end, value))
        return false;

    if(Utils::skipDesc(ptr, end, '%'))
        value *= 2.55;

    value = std::clamp(value, 0.0, 255.0);
    component = static_cast<int>(std::round(value));
    return true;
}

bool Parser::parseUrlFragment(const char*& ptr, const char* end, std::string& ref)
{
    if(!Utils::skipDesc(ptr, end, "url(")
        || !Utils::skipWs(ptr, end)) {
        return false;
    }

    switch(*ptr) {
    case '\'':
    case '"': {
        auto delim = *ptr;
        ++ptr; // delim
        Utils::skipWs(ptr, end);
        if(ptr >= end || *ptr != '#')
            return false;
        ++ptr; // #
        if(!Utils::readUntil(ptr, end, delim, ref))
            return false;
        ++ptr; // delim
        break;
    }

    case '#':
        ++ptr; // #
        Utils::readUntil(ptr, end, ')', ref);
        break;
    default:
        return false;
    }

    if(ptr >= end || *ptr != ')')
        return false;

    ++ptr; // )
    Utils::skipWs(ptr, end);
    return true;
}

bool Parser::parseTransform(const char*& ptr, const char* end, TransformType& type, double* values, int& count)
{
    int required = 0;
    int optional = 0;
    if(Utils::skipDesc(ptr, end, "matrix")) {
        type = TransformType::Matrix;
        required = 6;
        optional = 0;
    } else if(Utils::skipDesc(ptr, end, "rotate")) {
        type = TransformType::Rotate;
        required = 1;
        optional = 2;
    } else if(Utils::skipDesc(ptr, end, "scale")) {
        type = TransformType::Scale;
        required = 1;
        optional = 1;
    } else if(Utils::skipDesc(ptr, end, "skewX")) {
        type = TransformType::SkewX;
        required = 1;
        optional = 0;
    } else if(Utils::skipDesc(ptr, end, "skewY")) {
        type = TransformType::SkewY;
        required = 1;
        optional = 0;
    } else if(Utils::skipDesc(ptr, end, "translate")) {
        type = TransformType::Translate;
        required = 1;
        optional = 1;
    } else {
        return false;
    }

    Utils::skipWs(ptr, end);
    if(ptr >= end || *ptr != '(')
        return false;
    ++ptr;

    int maxCount = required + optional;
    count = 0;
    Utils::skipWs(ptr, end);
    while(count < maxCount) {
        if(!Utils::parseNumber(ptr, end, values[count]))
            break;
        ++count;
        Utils::skipWsComma(ptr, end);
    }

    if(ptr >= end || *ptr != ')' || !(count == required || count == maxCount))
        return false;
    ++ptr;

    return true;
}

static inline ElementID elementid(const std::string& name)
{
    static const std::map<std::string, ElementID> elementmap = {
        {"circle", ElementID::Circle},
        {"clipPath", ElementID::ClipPath},
        {"defs", ElementID::Defs},
        {"ellipse", ElementID::Ellipse},
        {"g", ElementID::G},
        {"line", ElementID::Line},
        {"linearGradient", ElementID::LinearGradient},
        {"marker", ElementID::Marker},
        {"mask", ElementID::Mask},
        {"path", ElementID::Path},
        {"pattern", ElementID::Pattern},
        {"polygon", ElementID::Polygon},
        {"polyline", ElementID::Polyline},
        {"radialGradient", ElementID::RadialGradient},
        {"rect", ElementID::Rect},
        {"stop", ElementID::Stop},
        {"style", ElementID::Style},
        {"solidColor", ElementID::SolidColor},
        {"svg", ElementID::Svg},
        {"symbol", ElementID::Symbol},
        {"use", ElementID::Use}
    };

    auto it = elementmap.find(name);
    if(it == elementmap.end())
        return ElementID::Unknown;
    return it->second;
}

static inline PropertyID csspropertyid(const std::string& name)
{
    static const std::map<std::string, PropertyID> csspropertymap = {
        {"clip-path", PropertyID::Clip_Path},
        {"clip-rule", PropertyID::Clip_Rule},
        {"color", PropertyID::Color},
        {"display", PropertyID::Display},
        {"fill", PropertyID::Fill},
        {"fill-opacity", PropertyID::Fill_Opacity},
        {"fill-rule", PropertyID::Fill_Rule},
        {"marker-end", PropertyID::Marker_End},
        {"marker-mid", PropertyID::Marker_Mid},
        {"marker-start", PropertyID::Marker_Start},
        {"mask", PropertyID::Mask},
        {"opacity", PropertyID::Opacity},
        {"overflow", PropertyID::Overflow},
        {"solid-color", PropertyID::Solid_Color},
        {"solid-opacity", PropertyID::Solid_Opacity},
        {"stop-color", PropertyID::Stop_Color},
        {"stop-opacity", PropertyID::Stop_Opacity},
        {"stroke", PropertyID::Stroke},
        {"stroke-dasharray", PropertyID::Stroke_Dasharray},
        {"stroke-dashoffset", PropertyID::Stroke_Dashoffset},
        {"stroke-linecap", PropertyID::Stroke_Linecap},
        {"stroke-linejoin", PropertyID::Stroke_Linejoin},
        {"stroke-miterlimit", PropertyID::Stroke_Miterlimit},
        {"stroke-opacity", PropertyID::Stroke_Opacity},
        {"stroke-width", PropertyID::Stroke_Width},
        {"visibility", PropertyID::Visibility}
    };

    auto it = csspropertymap.find(name);
    if(it == csspropertymap.end())
        return PropertyID::Unknown;
    return it->second;
}

static inline PropertyID propertyid(const std::string& name)
{
    static const std::map<std::string, PropertyID> propertymap = {
        {"class", PropertyID::Class},
        {"clipPathUnits", PropertyID::ClipPathUnits},
        {"cx", PropertyID::Cx},
        {"cy", PropertyID::Cy},
        {"d", PropertyID::D},
        {"fx", PropertyID::Fx},
        {"fy", PropertyID::Fy},
        {"gradientTransform", PropertyID::GradientTransform},
        {"gradientUnits", PropertyID::GradientUnits},
        {"height", PropertyID::Height},
        {"href", PropertyID::Href},
        {"id", PropertyID::Id},
        {"markerHeight", PropertyID::MarkerHeight},
        {"markerUnits", PropertyID::MarkerUnits},
        {"markerWidth", PropertyID::MarkerWidth},
        {"maskContentUnits", PropertyID::MaskContentUnits},
        {"maskUnits", PropertyID::MaskUnits},
        {"offset", PropertyID::Offset},
        {"orient", PropertyID::Orient},
        {"patternContentUnits", PropertyID::PatternContentUnits},
        {"patternTransform", PropertyID::PatternTransform},
        {"patternUnits", PropertyID::PatternUnits},
        {"points", PropertyID::Points},
        {"preserveAspectRatio", PropertyID::PreserveAspectRatio},
        {"r", PropertyID::R},
        {"refX", PropertyID::RefX},
        {"refY", PropertyID::RefY},
        {"rx", PropertyID::Rx},
        {"ry", PropertyID::Ry},
        {"spreadMethod", PropertyID::SpreadMethod},
        {"style", PropertyID::Style},
        {"transform", PropertyID::Transform},
        {"viewBox", PropertyID::ViewBox},
        {"width", PropertyID::Width},
        {"x", PropertyID::X},
        {"x1", PropertyID::X1},
        {"x2", PropertyID::X2},
        {"xlink:href", PropertyID::Href},
        {"y", PropertyID::Y},
        {"y1", PropertyID::Y1},
        {"y2", PropertyID::Y2}
    };

    auto it = propertymap.find(name);
    if(it == propertymap.end())
        return csspropertyid(name);
    return it->second;
}

bool RuleData::match(const Element* element) const
{
    if(m_selector.empty())
        return false;

    if(m_selector.size() == 1)
        return matchSimpleSelector(m_selector.front(), element);

    auto it = m_selector.rbegin();
    auto end = m_selector.rend();
    if(!matchSimpleSelector(*it, element))
        return false;
    ++it;

    while(it != end) {
        switch(it->combinator) {
        case SimpleSelector::Combinator::Child:
        case SimpleSelector::Combinator::Descendant:
            element = element->parent;
            break;
        case SimpleSelector::Combinator::DirectAdjacent:
        case SimpleSelector::Combinator::InDirectAdjacent:
            element = element->previousElement();
            break;
        }

        if(element == nullptr)
            return false;

        if(matchSimpleSelector(*it, element)) {
            ++it;
        } else if(it->combinator != SimpleSelector::Combinator::Descendant
            && it->combinator != SimpleSelector::Combinator::InDirectAdjacent) {
            return false;
        }
    }

    return true;
}

bool RuleData::matchSimpleSelector(const SimpleSelector& selector, const Element* element)
{
    if(selector.id != ElementID::Star && selector.id != element->id)
        return false;

    for(auto& sel : selector.attributeSelectors) {
        if(!matchAttributeSelector(sel, element)) {
            return false;
        }
    }

    for(auto& sel : selector.pseudoClassSelectors) {
        if(!matchPseudoClassSelector(sel, element)) {
            return false;
        }
    }

    return true;
}

bool RuleData::matchAttributeSelector(const AttributeSelector& selector, const Element* element)
{
    auto& value = element->get(selector.id);
    if(value.empty())
        return false;

    if(selector.matchType == AttributeSelector::MatchType::None)
        return true;

    if(selector.matchType == AttributeSelector::MatchType::Equal)
        return selector.value == value;

    if(selector.matchType == AttributeSelector::MatchType::Includes) {
        auto ptr = value.data();
        auto end = ptr + value.size();
        while(ptr < end) {
            auto start = ptr;
            while(ptr < end && !IS_WS(*ptr))
                ++ptr;
            if(selector.value == std::string(start, ptr))
                return true;
            Utils::skipWs(ptr, end);
        }

        return false;
    }

    auto starts_with = [](const std::string& string, const std::string& prefix) {
        if(prefix.empty() || prefix.size() > string.size())
            return false;
        return string.compare(0, prefix.size(), prefix) == 0;
    };

    auto ends_with = [](const std::string& string, const std::string& suffix) {
        if(suffix.empty() || suffix.size() > string.size())
            return false;
        return string.compare(string.size() - suffix.size(), suffix.size(), suffix) == 0;
    };

    if(selector.matchType == AttributeSelector::MatchType::DashMatch) {
        if(selector.value == value)
            return true;
        return starts_with(value, selector.value + '-');
    }

    if(selector.matchType == AttributeSelector::MatchType::StartsWith)
        return starts_with(value, selector.value);

    if(selector.matchType == AttributeSelector::MatchType::EndsWith)
        return ends_with(value, selector.value);

    if(selector.matchType == AttributeSelector::MatchType::Contains)
        return value.find(selector.value) != std::string::npos;

    return false;
}

bool RuleData::matchPseudoClassSelector(const PseudoClassSelector& selector, const Element* element)
{
    if(selector.type == PseudoClassSelector::Type::Empty)
        return element->children.empty();

    if(selector.type == PseudoClassSelector::Type::Root)
        return element->parent == nullptr;

    if(selector.type == PseudoClassSelector::Type::Is) {
        for(auto& selector : selector.subSelectors) {
            for(auto& sel : selector) {
                if(!matchSimpleSelector(sel, element)) {
                    return false;
                }
            }
        }

        return true;
    }

    if(selector.type == PseudoClassSelector::Type::Not) {
        for(auto& selector : selector.subSelectors) {
            for(auto& sel : selector) {
                if(matchSimpleSelector(sel, element)) {
                    return false;
                }
            }
        }

        return true;
    }

    if(selector.type == PseudoClassSelector::Type::FirstChild)
        return !element->previousElement();

    if(selector.type == PseudoClassSelector::Type::LastChild)
        return !element->nextElement();

    if(selector.type == PseudoClassSelector::Type::OnlyChild)
        return !(element->previousElement() || element->nextElement());

    if(selector.type == PseudoClassSelector::Type::FirstOfType) {
        auto sibling = element->previousElement();
        while(sibling) {
            if(sibling->id == element->id)
                return false;
            sibling = element->previousElement();
        }

        return true;
    }

    if(selector.type == PseudoClassSelector::Type::LastOfType) {
        auto sibling = element->nextElement();
        while(sibling) {
            if(sibling->id == element->id)
                return false;
            sibling = element->nextElement();
        }

        return true;
    }

    return false;
}

#define IS_STARTNAMECHAR(c) (IS_ALPHA(c) ||  (c) == '_' || (c) == ':')
#define IS_NAMECHAR(c) (IS_STARTNAMECHAR(c) || IS_NUM(c) || (c) == '-' || (c) == '.')
static inline bool readIdentifier(const char*& ptr, const char* end, std::string& value)
{
    if(ptr >= end || !IS_STARTNAMECHAR(*ptr))
        return false;

    auto start = ptr;
    ++ptr;
    while(ptr < end && IS_NAMECHAR(*ptr))
        ++ptr;

    value.assign(start, ptr);
    return true;
}

#define IS_CSS_STARTNAMECHAR(c) (IS_ALPHA(c) || (c) == '_' || (c) == '-')
#define IS_CSS_NAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || IS_NUM(c))
static inline bool readCSSIdentifier(const char*& ptr, const char* end, std::string& value)
{
    if(ptr >= end || !IS_CSS_STARTNAMECHAR(*ptr))
        return false;

    auto start = ptr;
    ++ptr;
    while(ptr < end && IS_CSS_NAMECHAR(*ptr))
        ++ptr;

    value.assign(start, ptr);
    return true;
}

bool StyleSheet::parse(const std::string& value)
{
    auto ptr = value.data();
    auto end = ptr + value.size();

    while(ptr < end) {
        Utils::skipWs(ptr, end);
        if(Utils::skipDesc(ptr, end, '@')) {
            if(!parseAtRule(ptr, end))
                return false;
            continue;
        }

        Rule rule;
        if(!parseRule(ptr, end, rule))
            return false;
        add(rule);
    }

    return true;
}

void StyleSheet::add(const Rule& rule)
{
    for(auto& selector : rule.selectors) {
        uint32_t specificity = 0;
        for(auto& simpleSelector : selector) {
            specificity += (simpleSelector.id == ElementID::Star) ? 0x0 : 0x1;
            for(auto& attributeSelector : simpleSelector.attributeSelectors) {
                specificity += (attributeSelector.id == PropertyID::Id) ? 0x10000 : 0x100;
            }
        }

        m_rules.emplace(selector, rule.declarations, specificity, m_position);
    }

    m_position += 1;
}

bool StyleSheet::parseAtRule(const char*& ptr, const char* end)
{
    int depth = 0;
    while(ptr < end) {
        auto ch = *ptr;
        ++ptr;
        if(ch == ';' && depth == 0)
            break;
        if(ch == '{') ++depth;
        else if(ch == '}' && depth > 0) {
            if(depth == 1)
                break;
            --depth;
        }
    }

    return true;
}

bool StyleSheet::parseRule(const char*& ptr, const char* end, Rule& rule)
{
    if(!parseSelectors(ptr, end, rule.selectors))
        return false;

    if(!parseDeclarations(ptr, end, rule.declarations))
        return false;

    return true;
}

bool StyleSheet::parseSelectors(const char*& ptr, const char* end, SelectorList& selectors)
{
    Selector selector;
    if(!parseSelector(ptr, end, selector))
        return false;
    selectors.push_back(std::move(selector));

    while(Utils::skipDesc(ptr, end, ',')) {
        Utils::skipWs(ptr, end);
        Selector selector;
        if(!parseSelector(ptr, end, selector))
            return false;
        selectors.push_back(std::move(selector));
    }

    return true;
}

bool StyleSheet::parseDeclarations(const char*& ptr, const char* end, DeclarationList& declarations)
{
    if(!Utils::skipDesc(ptr, end, '{'))
        return false;

    Utils::skipWs(ptr, end);
    do {
        std::string name;
        if(!readCSSIdentifier(ptr, end, name))
            return false;
        Utils::skipWs(ptr, end);
        if(!Utils::skipDesc(ptr, end, ':'))
            return false;
        Utils::skipWs(ptr, end);
        auto start = ptr;
        while(ptr < end && !(*ptr == '!' || *ptr == ';' || *ptr == '}'))
            ++ptr;

        Declaration declaration;
        declaration.specificity = 0x10;
        declaration.id = csspropertyid(name);
        declaration.value.assign(start, Utils::rtrim(start, ptr));
        if(Utils::skipDesc(ptr, end, '!')) {
            if(!Utils::skipDesc(ptr, end, "important"))
                return false;
            declaration.specificity = 0x1000;
        }

        if(declaration.id != PropertyID::Unknown)
            declarations.push_back(std::move(declaration));
        Utils::skipWsDelimiter(ptr, end, ';');
    } while(ptr < end && *ptr != '}');

    return Utils::skipDesc(ptr, end, '}');
}

#define IS_SELECTOR_STARTNAMECHAR(c) (IS_CSS_STARTNAMECHAR(c) || (c) == '*' || (c) == '#' || (c) == '.' || (c) == '[' || (c) == ':')
bool StyleSheet::parseSelector(const char*& ptr, const char* end, Selector& selector)
{
    do {
        SimpleSelector simpleSelector;
        if(!parseSimpleSelector(ptr, end, simpleSelector))
            return false;
        selector.push_back(std::move(simpleSelector));
        Utils::skipWs(ptr, end);
    } while(ptr < end && IS_SELECTOR_STARTNAMECHAR(*ptr));

    return true;
}

bool StyleSheet::parseSimpleSelector(const char*& ptr, const char* end, SimpleSelector& simpleSelector)
{
    std::string name;
    if(Utils::skipDesc(ptr, end, '*'))
        simpleSelector.id = ElementID::Star;
    else if(readCSSIdentifier(ptr, end, name))
        simpleSelector.id = elementid(name);

    while(ptr < end) {
        if(Utils::skipDesc(ptr, end, '#')) {
            AttributeSelector a;
            a.id = PropertyID::Id;
            a.matchType = AttributeSelector::MatchType::Equal;
            if(!readCSSIdentifier(ptr, end, a.value))
                return false;
            simpleSelector.attributeSelectors.push_back(std::move(a));
            continue;
        }

        if(Utils::skipDesc(ptr, end, '.')) {
            AttributeSelector a;
            a.id = PropertyID::Class;
            a.matchType = AttributeSelector::MatchType::Includes;
            if(!readCSSIdentifier(ptr, end, a.value))
                return false;
            simpleSelector.attributeSelectors.push_back(std::move(a));
            continue;
        }

        if(Utils::skipDesc(ptr, end, '[')) {
            Utils::skipWs(ptr, end);
            if(!readCSSIdentifier(ptr, end, name))
                return false;
            AttributeSelector a;
            a.id = propertyid(name);
            if(Utils::skipDesc(ptr, end, '='))
                a.matchType = AttributeSelector::MatchType::Equal;
            else if(Utils::skipDesc(ptr, end, "~="))
                a.matchType = AttributeSelector::MatchType::Includes;
            else if(Utils::skipDesc(ptr, end, "|="))
                a.matchType = AttributeSelector::MatchType::DashMatch;
            else if(Utils::skipDesc(ptr, end, "^="))
                a.matchType = AttributeSelector::MatchType::StartsWith;
            else if(Utils::skipDesc(ptr, end, "$="))
                a.matchType = AttributeSelector::MatchType::EndsWith;
            else if(Utils::skipDesc(ptr, end, "*="))
                a.matchType = AttributeSelector::MatchType::Contains;
            if(a.matchType != AttributeSelector::MatchType::None) {
                Utils::skipWs(ptr, end);
                if(!readCSSIdentifier(ptr, end, a.value)) {
                    if(ptr >= end || !(*ptr == '\"' || *ptr == '\''))
                        return false;

                    auto quote = *ptr;
                    ++ptr;
                    if(!Utils::readUntil(ptr, end, quote, a.value))
                        return false;
                    ++ptr;
                }
            }

            Utils::skipWs(ptr, end);
            if(!Utils::skipDesc(ptr, end, ']'))
                return false;
            simpleSelector.attributeSelectors.push_back(std::move(a));
            continue;
        }

        if(Utils::skipDesc(ptr, end, ':')) {
            if(!readCSSIdentifier(ptr, end, name))
                return false;
            PseudoClassSelector selector;
            if(name.compare("empty") == 0)
                selector.type = PseudoClassSelector::Type::Empty;
            else if(name.compare("root") == 0)
                selector.type = PseudoClassSelector::Type::Root;
            else if(name.compare("not") == 0)
                selector.type = PseudoClassSelector::Type::Not;
            else if(name.compare("first-child") == 0)
                selector.type = PseudoClassSelector::Type::FirstChild;
            else if(name.compare("last-child") == 0)
                selector.type = PseudoClassSelector::Type::LastChild;
            else if(name.compare("only-child") == 0)
                selector.type = PseudoClassSelector::Type::OnlyChild;
            else if(name.compare("first-of-type") == 0)
                selector.type = PseudoClassSelector::Type::FirstOfType;
            else if(name.compare("last-of-type") == 0)
                selector.type = PseudoClassSelector::Type::LastOfType;
            else if(name.compare("only-of-type") == 0)
                selector.type = PseudoClassSelector::Type::OnlyOfType;
            if(selector.type == PseudoClassSelector::Type::Is || selector.type == PseudoClassSelector::Type::Not) {
                if(!Utils::skipDesc(ptr, end, '('))
                    return false;

                Utils::skipWs(ptr, end);
                if(!parseSelectors(ptr, end, selector.subSelectors))
                    return false;

                Utils::skipWs(ptr, end);
                if(!Utils::skipDesc(ptr, end, ')')) {
                    return false;
                }
            }

            simpleSelector.pseudoClassSelectors.push_back(std::move(selector));
            continue;
        }

        break;
    }

    Utils::skipWs(ptr, end);
    if(Utils::skipDesc(ptr, end, '>'))
        simpleSelector.combinator = SimpleSelector::Combinator::Child;
    else if(Utils::skipDesc(ptr, end, '+'))
        simpleSelector.combinator = SimpleSelector::Combinator::DirectAdjacent;
    else if(Utils::skipDesc(ptr, end, '~'))
        simpleSelector.combinator = SimpleSelector::Combinator::InDirectAdjacent;

    return true;
}

static inline std::unique_ptr<Element> createElement(ElementID id)
{
    switch(id) {
    case ElementID::Svg:
        return std::make_unique<SVGElement>();
    case ElementID::Path:
        return std::make_unique<PathElement>();
    case ElementID::G:
        return std::make_unique<GElement>();
    case ElementID::Rect:
        return std::make_unique<RectElement>();
    case ElementID::Circle:
        return std::make_unique<CircleElement>();
    case ElementID::Ellipse:
        return std::make_unique<EllipseElement>();
    case ElementID::Line:
        return std::make_unique<LineElement>();
    case ElementID::Defs:
        return std::make_unique<DefsElement>();
    case ElementID::Polygon:
        return std::make_unique<PolygonElement>();
    case ElementID::Polyline:
        return std::make_unique<PolylineElement>();
    case ElementID::Stop:
        return std::make_unique<StopElement>();
    case ElementID::LinearGradient:
        return std::make_unique<LinearGradientElement>();
    case ElementID::RadialGradient:
        return std::make_unique<RadialGradientElement>();
    case ElementID::Symbol:
        return std::make_unique<SymbolElement>();
    case ElementID::Use:
        return std::make_unique<UseElement>();
    case ElementID::Pattern:
        return std::make_unique<PatternElement>();
    case ElementID::Mask:
        return std::make_unique<MaskElement>();
    case ElementID::ClipPath:
        return std::make_unique<ClipPathElement>();
    case ElementID::SolidColor:
        return std::make_unique<SolidColorElement>();
    case ElementID::Marker:
        return std::make_unique<MarkerElement>();
    case ElementID::Style:
        return std::make_unique<StyleElement>();
    default:
        break;
    }

    return nullptr;
}

static inline bool decodeText(const char* ptr, const char* end, std::string& value)
{
    value.clear();
    while(ptr < end) {
        auto ch = *ptr;
        ++ptr;
        if(ch != '&') {
            value.push_back(ch);
            continue;
        }

        if(Utils::skipDesc(ptr, end, '#')) {
            int base = 10;
            if(Utils::skipDesc(ptr, end, 'x'))
                base = 16;

            unsigned int cp;
            if(!Utils::parseInteger(ptr, end, cp, base))
                return false;

            char c[5] = {0, 0, 0, 0, 0};
            if(cp < 0x80) {
                c[1] = 0;
                c[0] = cp;
            } else if(cp < 0x800) {
                c[2] = 0;
                c[1] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[0] = cp | 0xC0;
            } else if(cp < 0x10000) {
                c[3] = 0;
                c[2] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[1] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[0] = cp | 0xE0;
            } else if(cp < 0x200000) {
                c[4] = 0;
                c[3] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[2] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[1] = (cp & 0x3F) | 0x80;
                cp >>= 6;
                c[0] = cp | 0xF0;
            }

            value.append(c);
        } else {
            if(Utils::skipDesc(ptr, end, "amp"))
                value.push_back('&');
            else if(Utils::skipDesc(ptr, end, "lt"))
                value.push_back('<');
            else if(Utils::skipDesc(ptr, end, "gt"))
                value.push_back('>');
            else if(Utils::skipDesc(ptr, end, "quot"))
                value.push_back('\"');
            else if(Utils::skipDesc(ptr, end, "apos"))
                value.push_back('\'');
            else {
                return false;
            }
        }

        if(!Utils::skipDesc(ptr, end, ';')) {
            return false;
        }
    }

    return true;
}

static inline void parseStyle(const std::string& string, Element* element)
{
    auto ptr = string.data();
    auto end = ptr + string.size();

    std::string name;
    std::string value;
    Utils::skipWs(ptr, end);
    while(ptr < end && readCSSIdentifier(ptr, end, name)) {
        Utils::skipWs(ptr, end);
        if(!Utils::skipDesc(ptr, end, ':'))
            return;
        Utils::skipWs(ptr, end);
        auto start = ptr;
        while(ptr < end && *ptr != ';')
            ++ptr;
        value.assign(start, Utils::rtrim(start, ptr));
        auto id = csspropertyid(name);
        if(id != PropertyID::Unknown)
            element->set(id, value, 0x100);
        Utils::skipWsDelimiter(ptr, end, ';');
    }
}

static inline void removeComments(std::string& value)
{
    auto start = value.find("/*");
    while(start != std::string::npos) {
        auto end = value.find("*/", start + 2);
        value.erase(start, end - start + 2);
        start = value.find("/*");
    }
}

TreeBuilder::TreeBuilder() = default;

TreeBuilder::~TreeBuilder() = default;

bool TreeBuilder::parse(const char* data, std::size_t size)
{
    auto ptr = data;
    auto end = ptr + size;

    StyleSheet styleSheet;
    Element* current = nullptr;
    std::string name;
    std::string value;
    int ignoring = 0;
    auto handleText = [&](const char* start, const char* end, bool in_cdata) {
        if(ignoring > 0 || current == nullptr || current->id != ElementID::Style)
            return;

        if(in_cdata)
            value.assign(start, end);
        else
            decodeText(start, end, value);

        removeComments(value);
        styleSheet.parse(value);
    };

    while(ptr < end) {
        auto start = ptr;
        if(!Utils::skipUntil(ptr, end, '<'))
            break;

        handleText(start, ptr, false);
        ptr += 1;

        if(ptr < end && *ptr == '/') {
            if(current == nullptr && ignoring == 0)
                return false;

            ++ptr;
            if(!readIdentifier(ptr, end, name))
                return false;

            Utils::skipWs(ptr, end);
            if(ptr >= end || *ptr != '>')
                return false;

            if(ignoring > 0)
                --ignoring;
            else
                current = current->parent;

            ++ptr;
            continue;
        }

        if(ptr < end && *ptr == '?') {
            ++ptr;
            if(!readIdentifier(ptr, end, name))
                return false;

            if(!Utils::skipUntil(ptr, end, "?>"))
                return false;

            ptr += 2;
            continue;
        }

        if(ptr < end && *ptr == '!') {
            ++ptr;
            if(Utils::skipDesc(ptr, end, "--")) {
                start = ptr;
                if(!Utils::skipUntil(ptr, end, "-->"))
                    return false;

                handleText(start, ptr, false);
                ptr += 3;
                continue;
            }

            if(Utils::skipDesc(ptr, end, "[CDATA[")) {
                start = ptr;
                if(!Utils::skipUntil(ptr, end, "]]>"))
                    return false;

                handleText(start, ptr, true);
                ptr += 3;
                continue;
            }

            if(Utils::skipDesc(ptr, end, "DOCTYPE")) {
                while(ptr < end && *ptr != '>') {
                    if(*ptr == '[') {
                        ++ptr;
                        int depth = 1;
                        while(ptr < end && depth > 0) {
                            if(*ptr == '[') ++depth;
                            else if(*ptr == ']') --depth;
                            ++ptr;
                        }
                    } else {
                        ++ptr;
                    }
                }

                if(ptr >= end || *ptr != '>')
                    return false;

                ptr += 1;
                continue;
            }

            return false;
        }

        if(!readIdentifier(ptr, end, name))
            return false;

        auto id = ElementID::Unknown;
        if(ignoring == 0)
            id = elementid(name);
        if(id == ElementID::Unknown)
            ++ignoring;

        Element* element = nullptr;
        if(ignoring == 0) {
            if(m_rootElement && current == nullptr)
                return false;

            if(m_rootElement == nullptr) {
                if(id != ElementID::Svg)
                    return false;
                m_rootElement = std::make_unique<SVGElement>();
                element = m_rootElement.get();
            } else {
                auto child = createElement(id);
                element = child.get();
                current->addChild(std::move(child));
            }
        }

        Utils::skipWs(ptr, end);
        while(ptr < end && readIdentifier(ptr, end, name)) {
            Utils::skipWs(ptr, end);
            if(ptr >= end || *ptr != '=')
                return false;
            ++ptr;

            Utils::skipWs(ptr, end);
            if(ptr >= end || !(*ptr == '\"' || *ptr == '\''))
                return false;

            auto quote = *ptr;
            ++ptr;
            Utils::skipWs(ptr, end);
            start = ptr;
            while(ptr < end && *ptr != quote)
                ++ptr;

            if(ptr >= end || *ptr != quote)
                return false;

            auto id = PropertyID::Unknown;
            if(element != nullptr)
                id = propertyid(name);
            if(id != PropertyID::Unknown) {
                decodeText(start, Utils::rtrim(start, ptr), value);
                if(id == PropertyID::Style) {
                    removeComments(value);
                    parseStyle(value, element);
                } else {
                    if(id == PropertyID::Id)
                        m_idCache.emplace(value, element);
                    element->set(id, value, 0x1);
                }
            }

            ++ptr;
            Utils::skipWs(ptr, end);
        }

        if(ptr < end && *ptr == '>') {
            if(element != nullptr)
                current = element;

            ++ptr;
            continue;
        }

        if(ptr < end && *ptr == '/') {
            ++ptr;
            if(ptr >= end || *ptr != '>')
                return false;

            if(ignoring > 0)
                --ignoring;

            ++ptr;
            continue;
        }

        return false;
    }

    if(!m_rootElement || ptr < end || ignoring > 0)
        return false;

    if(!styleSheet.empty()) {
        m_rootElement->transverse([&styleSheet](Node* node) {
            if(node->isText())
                return true;

            auto element = static_cast<Element*>(node);
            for(auto& rule : styleSheet.rules()) {
                if(rule.match(element)) {
                    for(auto& declaration : rule.declarations()) {
                        element->set(declaration.id, declaration.value, declaration.specificity);
                    }
                }
            }

            return true;
        });
    }

    return true;
}

Element* TreeBuilder::getElementById(const std::string& id) const
{
    auto it = m_idCache.find(id);
    if(it == m_idCache.end())
        return nullptr;
    return it->second;
}

std::unique_ptr<LayoutSymbol> TreeBuilder::build() const
{
    return m_rootElement->build(this);
}

} // namespace lunasvg
