#define CSS_BORDER_NONE 0
#define CSS_BORDER_HIDDEN 1
#define CSS_BORDER_DOTTED 2
#define CSS_BORDER_DASHED 3
#define CSS_BORDER_SOLID 4
#define CSS_BORDER_DOUBLE 5
#define CSS_BORDER_GROOVE 6
#define CSS_BORDER_RIDGE 7
#define CSS_BORDER_INSET 8
#define CSS_BORDER_OUTSET 9

#define CSS_DISPLAY_NONE 0
#define CSS_DISPLAY_BLOCK 1
#define CSS_DISPLAY_INLINE 2
#define CSS_DISPLAY_INLINE_BLOCK 3

#define CSS_DISTANCE_UNDEFINED 0
#define CSS_DISTANCE_PIXELS 1
#define CSS_DISTANCE_EM 2
#define CSS_DISTANCE_PERCENT 3
#define CSS_DISTANCE_AUTO 4

#define CSS_TEXT_ALIGN_LEFT 0
#define CSS_TEXT_ALIGN_CENTER 1
#define CSS_TEXT_ALIGN_RIGHT 2

#define CSS_TOKENIZER_STATE_CONSUME_MATCH 0
#define CSS_TOKENIZER_STATE_CONSUME_PROPERTY 1
#define CSS_TOKENIZER_STATE_CONSUME_VALUE 2
#define CSS_TOKENIZER_SKIP_AT_RULE 3
#define CSS_TOKENIZER_SKIP_COMMENT 4

U8* @css_named_colors_buffer = FileRead("M:/System/Libraries/Css/NamedColors.json");
JsonObject* @css_named_colors = Json.Parse(@css_named_colors_buffer, erythros_mem_task);
Free(@css_named_colors_buffer);

class @css_side
{
    F64 value;
    I64 type; // CSS_DISTANCE_
};

class @css_area
{
    @css_side top;
    @css_side bottom;
    @css_side left;
    @css_side right;
};

class @css_radius
{
    @css_side topLeft;
    @css_side topRight;
    @css_side bottomLeft;
    @css_side bottomRight;
}

class @css_border : @css_area
{
    U32 topColor;
    U32 bottomColor;
    U32 leftColor;
    U32 rightColor;
    I64 topStyle;
    I64 bottomStyle;
    I64 leftStyle;
    I64 rightStyle;
    @css_radius radius;
};

class @css_tokenizer
{
    U8* buffer;
    I64 pos;
    I64 size;
    I64 state;
    I64 previous_state;
    I64 in_quote_char;
    Bool in_paren;
    CFifoU8* match_fifo;
    CFifoU8* property_fifo;
    CFifoU8* value_fifo;
    JsonObject* current_rule;
    JsonArray* current_values;
    CTask* mem_task;
};

// U8* @custom_css_rules_buffer = FileRead("M:/System/Libraries/Css/CustomRules.json");
JsonObject* @custom_css_rules = Json.Parse("{}", erythros_mem_task);
// Free(@custom_css_rules_buffer);

U0 @css_init_current_values(@css_tokenizer* t)
{
    t->current_values = Json.CreateArray(erythros_mem_task);
}

U0 @css_init_current_rule(@css_tokenizer* t)
{
    t->current_rule = Json.CreateObject(erythros_mem_task);
    t->current_rule->set("matches", Json.CreateArray(erythros_mem_task), JSON_ARRAY);
    t->current_rule->set("properties", Json.CreateObject(erythros_mem_task), JSON_OBJECT);
}

U0 @css_init_tokenizer(@css_tokenizer* t, U8* buffer, I64 size, CTask* mem_task = NULL)
{
    t->buffer = buffer;
    t->pos = 0;
    t->in_quote_char = 0;
    t->in_paren = FALSE;
    t->size = size;
    t->state = CSS_TOKENIZER_STATE_CONSUME_MATCH;
    t->match_fifo = FifoU8New(1024);
    t->mem_task = mem_task;
    t->property_fifo = FifoU8New(1024);
    t->value_fifo = FifoU8New(1024);
    @css_init_current_rule(t);
}

Bool @css_try_append_match(@css_tokenizer* t)
{
    U8* match;
    if (FifoU8Cnt(t->match_fifo)) {
        match = @json_string_from_fifo(t->match_fifo, Fs);
        String.Trim(match);
        t->current_rule->a("matches")->append(match);
        return TRUE;
    }
    return FALSE;
}

Bool @css_try_append_value(@css_tokenizer* t)
{
    U8* value;
    if (FifoU8Cnt(t->value_fifo)) {
        value = @json_string_from_fifo(t->value_fifo, Fs);
        t->current_values->append(value);
        return TRUE;
    }
    return FALSE;
}

Bool @css_try_set_property(@css_tokenizer* t)
{
    U8* property;
    if (FifoU8Cnt(t->property_fifo)) {
        property = CAlloc(FifoU8Cnt(t->property_fifo) + 1, t->mem_task);
        while (FifoU8Cnt(t->property_fifo))
            FifoU8Rem(t->property_fifo, property + StrLen(property));
        t->current_rule->o("properties")->set(property, t->current_values, JSON_ARRAY);
        return TRUE;
    }
    return FALSE;
}

U0 @css_tokenize_and_create_rules_from_buffer(JsonArray* rules, U8* buffer, I64 size, CTask* mem_task = NULL)
{
    @css_tokenizer t;
    @css_init_tokenizer(&t, buffer, size, mem_task);
    I64 brace_depth = 0;
    JsonItem* item;
    U8 check_whitespace_char;
    while (t.pos < t.size) {
        I64 token = t.buffer[t.pos];
        switch (t.state) {
        case CSS_TOKENIZER_SKIP_COMMENT:
            if (token == '*' && t.buffer[t.pos + 1] == '/') {
                ++t.pos;
                t.state = t.previous_state;
                goto @css_tokenizer_continue;
            }
            break;
        case CSS_TOKENIZER_SKIP_AT_RULE:
            switch (token) {
            case '{':
                brace_depth++;
                break;
            case '}':
                brace_depth--;
                if (brace_depth <= 0) {
                    t.state = CSS_TOKENIZER_STATE_CONSUME_MATCH;
                    goto @css_tokenizer_continue;
                }
                break;
            default:
                break;
            }
            break;
        case CSS_TOKENIZER_STATE_CONSUME_VALUE:
            switch (token) {
            case '(':
            case ')':
                t.in_paren = T(token == '(', TRUE, FALSE);
                FifoU8Ins(t.value_fifo, token);
                goto @css_tokenizer_continue;
            case '\'':
            case '"':
                if (t.in_quote_char == token) {
                    t.in_quote_char = NULL;
                    FifoU8Ins(t.value_fifo, token);
                    goto @css_tokenizer_continue;
                }
                if (!t.in_quote_char) {
                    t.in_quote_char = token;
                }
                FifoU8Ins(t.value_fifo, token);
                goto @css_tokenizer_continue;
            case '/':
                if (t.in_quote_char) {
                    FifoU8Ins(t.value_fifo, token);
                    goto @css_tokenizer_continue;
                }
                if (t.buffer[t.pos + 1] == '*') {
                    ++t.pos;
                    t.previous_state = t.state;
                    t.state = CSS_TOKENIZER_SKIP_COMMENT;
                    goto @css_tokenizer_continue;
                }
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                if (t.in_paren) {
                    goto @css_tokenizer_continue;
                }
            case ',':
                if (t.in_quote_char || t.in_paren) {
                    FifoU8Ins(t.value_fifo, token);
                    goto @css_tokenizer_continue;
                }
                if (FifoU8Cnt(t.value_fifo))
                    @css_try_append_value(&t);
                break;
            case '}':
                if (t.in_quote_char) {
                    FifoU8Ins(t.value_fifo, token);
                    goto @css_tokenizer_continue;
                }
                @css_try_append_value(&t);
                if (FifoU8Cnt(t.property_fifo))
                    @css_try_set_property(&t);
                if (t.current_rule->a("matches")->length) {
                    rules->append(t.current_rule);
                    @css_init_current_rule(&t);
                }
                t.state = CSS_TOKENIZER_STATE_CONSUME_MATCH;
                goto @css_tokenizer_continue;
            case ';':
                if (t.in_quote_char) {
                    FifoU8Ins(t.value_fifo, token);
                    goto @css_tokenizer_continue;
                }
                @css_try_append_value(&t);
                if (FifoU8Cnt(t.property_fifo))
                    @css_try_set_property(&t);
                t.state = CSS_TOKENIZER_STATE_CONSUME_PROPERTY;
                goto @css_tokenizer_continue;
            default:
                FifoU8Ins(t.value_fifo, token);
                break;
            }
            break;
        case CSS_TOKENIZER_STATE_CONSUME_PROPERTY:
            switch (token) {
            case '/':
                if (t.buffer[t.pos + 1] == '*') {
                    ++t.pos;
                    t.previous_state = t.state;
                    t.state = CSS_TOKENIZER_SKIP_COMMENT;
                    goto @css_tokenizer_continue;
                }
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                if (FifoU8Cnt(t.property_fifo)) {
                    PrintErr("Invalid token in CSS property at pos %d, token: '%c'\n", t.pos, token);
                    return;
                }
                break;
            case '}':
                if (t.current_rule->a("matches")->length) {
                    rules->append(t.current_rule);
                    @css_init_current_rule(&t);
                }
                t.state = CSS_TOKENIZER_STATE_CONSUME_MATCH;
                goto @css_tokenizer_continue;
            case ':':
                if (!FifoU8Cnt(t.property_fifo)) {
                    PrintErr("CSS property is not defined at pos %d, token: '%c'\n", t.pos, token);
                    return;
                }
                @css_init_current_values(&t);
                t.state = CSS_TOKENIZER_STATE_CONSUME_VALUE;
                goto @css_tokenizer_continue;
            default:
                FifoU8Ins(t.property_fifo, token);
                break;
            }
            break;
        case CSS_TOKENIZER_STATE_CONSUME_MATCH:
            switch (token) {
            case '/':
                if (t.buffer[t.pos + 1] == '*') {
                    ++t.pos;
                    t.previous_state = t.state;
                    t.state = CSS_TOKENIZER_SKIP_COMMENT;
                    goto @css_tokenizer_continue;
                }
            case '@':
                t.state = CSS_TOKENIZER_SKIP_AT_RULE;
                goto @css_tokenizer_continue;
            case ' ':
            case '\t':
            case '\r':
            case '\n':
                FifoU8Last(t.match_fifo, &check_whitespace_char);
                if (check_whitespace_char != ' ') {
                    FifoU8Ins(t.match_fifo, ' ');
                }
                break;
            case ',':
                if (FifoU8Cnt(t.match_fifo))
                    @css_try_append_match(&t);
                break;
            case '{':
                @css_try_append_match(&t);
                if (!t.current_rule->a("matches")->length) {
                    PrintErr("CSS match string is not defined at pos %d, token: '%c'\n", t.pos, token);
                    return;
                }
                t.state = CSS_TOKENIZER_STATE_CONSUME_PROPERTY;
                goto @css_tokenizer_continue;
            default:
                FifoU8Ins(t.match_fifo, token);
                break;
            }
            break;
        }
        @css_tokenizer_continue : ++t.pos;
    }
    FifoU8Del(t.match_fifo);
    FifoU8Del(t.property_fifo);
    FifoU8Del(t.value_fifo);
}
