/* Copyright 2022 the SumatraPDF project authors (see AUTHORS file).
   License: Simplified BSD (see COPYING.BSD) */

#include "utils/BaseUtil.h"
#include "utils/HtmlParserLookup.h"
#include "utils/CssParser.h"

// TODO: the following parser doesn't comply yet with
// http://www.w3.org/TR/CSS21/syndata.html#syntax

// return true if skipped
static bool SkipWsAndComments(const char*& s, const char* end) {
    const char* start = s;
    for (; s < end && str::IsWs(*s); s++) {
        ;
    }
    while (s + 2 <= end && s[0] == '/' && s[1] == '*') {
        for (s += 2; s < end; s++) {
            if (s + 2 <= end && s[0] == '*' && s[1] == '/') {
                s += 2;
                break;
            }
        }
        for (; s < end && str::IsWs(*s); s++) {
            ;
        }
    }
    return start != s;
}

// returns false if there was no closing quotation mark
static bool SkipQuotedString(const char*& s, const char* end) {
    char quote = *s;
    while (++s < end && *s != quote) {
        if (*s == '\\') {
            s++;
            if (s >= end) {
                return false;
            }
        }
    }
    if (s == end) {
        return false;
    }
    s++;
    return true;
}

static bool SkipBlock(const char*& s, const char* end) {
    ReportIf(s >= end || *s != '{');
    s++;
    while (s < end && *s != '}') {
        if (*s == '"' || *s == '\'') {
            if (!SkipQuotedString(s, end)) {
                return false;
            }
        } else if (*s == '{') {
            if (!SkipBlock(s, end)) {
                return false;
            }
        } else if (*s == '\\' && s < end - 1) {
            s += 2;
        } else if (!SkipWsAndComments(s, end)) {
            s++;
        }
    }
    if (s == end) {
        return false;
    }
    s++;
    return true;
}

bool CssPullParser::NextRule() {
    if (inProps) {
        while (NextProperty()) {
            ;
        }
    }
    ReportIf(inProps && currPos < end);
    if (inlineStyle || currPos == end) {
        return false;
    }

    if (currPos == s) {
        SkipWsAndComments(currPos, end);
        if (currPos + 4 < end && str::StartsWith(currPos, "<!--")) {
            currPos += 4;
        }
    }

    SkipWsAndComments(currPos, end);
    currSel = currPos;
    // skip selectors
    while (currPos < end && *currPos != '{') {
        if (*currPos == '"' || *currPos == '\'') {
            if (!SkipQuotedString(currPos, end)) {
                break;
            }
        } else if (*currPos == ';') {
            currPos++;
            SkipWsAndComments(currPos, end);
            currSel = currPos;
        } else if (!SkipWsAndComments(currPos, end)) {
            currPos++;
        }
    }

    if (currPos == end) {
        currSel = nullptr;
        return false;
    }
    selEnd = currPos++;
    inProps = true;
    return true;
}

const CssSelector* CssPullParser::NextSelector() {
    if (!currSel) {
        return nullptr;
    }
    SkipWsAndComments(currSel, selEnd);
    if (currSel == selEnd) {
        return nullptr;
    }

    sel.s = currSel;
    // skip single selector
    const char* sEnd = currSel;
    while (currSel < selEnd && *currSel != ',') {
        if (*currSel == '"' || *currSel == '\'') {
            bool ok = SkipQuotedString(currSel, selEnd);
            ReportIf(!ok);
            sEnd = currSel;
        } else if (*currSel == '\\' && currSel < selEnd - 1) {
            currSel += 2;
            sEnd = currSel;
        } else if (!SkipWsAndComments(currSel, selEnd)) {
            sEnd = ++currSel;
        }
    }
    if (currSel < selEnd) {
        currSel++;
    }

    sel.sLen = sEnd - sel.s;
    sel.tag = Tag_NotFound;
    sel.clazz = nullptr;
    sel.clazzLen = 0;

    // parse "*", "el", ".class" and "el.class"
    const char* c = sEnd;
    for (; c > sel.s && (isalnum((u8) * (c - 1)) || *(c - 1) == '-'); c--) {
        ;
    }
    if (c > sel.s && *(c - 1) == '.') {
        sel.clazz = c;
        sel.clazzLen = sEnd - c;
        c--;
    }
    for (; c > sel.s && (isalnum((u8) * (c - 1)) || *(c - 1) == '-'); c--) {
        ;
    }
    if (sel.clazz - 1 == sel.s) {
        sel.tag = Tag_Any;
    } else if (c == (sel.clazz ? sel.clazz - 1 : sEnd) && c == sel.s + 1 && *sel.s == '*') {
        sel.tag = Tag_Any;
    } else if (c == sel.s) {
        sel.tag = FindHtmlTag(sel.s, sel.clazz ? sel.clazz - sel.s - 1 : sel.sLen);
    }

    return &sel;
}

const CssProperty* CssPullParser::NextProperty() {
    if (currPos == s) {
        inlineStyle = inProps = true;
    } else if (!inProps) {
        return nullptr;
    }

GetNextProperty:
    SkipWsAndComments(currPos, end);
    if (currPos == end) {
        return nullptr;
    }
    if (*currPos == '}') {
        currPos++;
        inProps = false;
        return nullptr;
    }
    if (*currPos == '{') {
        if (!SkipBlock(currPos, end)) {
            return nullptr;
        }
        goto GetNextProperty;
    }
    if (*currPos == ';') {
        currPos++;
        goto GetNextProperty;
    }
    const char* name = currPos;
    // skip identifier
    while (currPos < end && !str::IsWs(*currPos) && *currPos != ':' && *currPos != ';' && *currPos != '{' &&
           *currPos != '}') {
        currPos++;
    }
    SkipWsAndComments(currPos, end);
    if (currPos == end || *currPos != ':') {
        goto GetNextProperty;
    }
    prop.type = FindCssProp(name, currPos - name);
    currPos++;
    SkipWsAndComments(currPos, end);

    prop.s = currPos;
    // skip value
    const char* valEnd = currPos;
    while (currPos < end && *currPos != ';' && *currPos != '}') {
        if (*currPos == '"' || *currPos == '\'') {
            if (!SkipQuotedString(currPos, end)) {
                return nullptr;
            }
            valEnd = currPos;
        } else if (*currPos == '{') {
            if (!SkipBlock(currPos, end)) {
                return nullptr;
            }
            valEnd = currPos;
        } else if (*currPos == '\\' && currPos < end - 1) {
            currPos += 2;
            valEnd = currPos;
        } else if (!SkipWsAndComments(currPos, end)) {
            valEnd = ++currPos;
        }
    }
    prop.sLen = valEnd - prop.s;

    return &prop;
}
