#define SFT_GENERIC         1

public U8 **StrFileRead(U8 *name, I64 *_max_num=NULL, U8 **_colors=NULL, Bool no_nums=FALSE)
{
    CDoc        *doc = DocRead(name, DOCF_DBL_DOLLARS | DOCF_NO_CURSOR);
    CDocEntry   *doc_e = doc->head.next;
    I64          i, max_num = 0;
    U8          *ptr, **res, *colors;

    while (doc_e != doc)
    {
        if (doc_e->type_u8 == DOCT_TEXT)
        {
            if (no_nums)
                i = ++max_num;
            else
            {
                i = Str2I64(doc_e->tag,, &ptr);
                if (i > max_num)
                    max_num = i;
                if (*ptr == ', ')
                    ptr++;
                ptr = StrNew(ptr);
                Free(doc_e->tag);
                doc_e->tag = ptr;
            }
            doc_e->user_data = i;
        }
        doc_e = doc_e->next;
    }

    res = CAlloc(sizeof(U8 *)  * (max_num + 1));
    colors = CAlloc(sizeof(U8) * (max_num + 1));
    doc_e = doc->head.next;
    while (doc_e != doc)
    {
        if (doc_e->type_u8 == DOCT_TEXT && 0 <= doc_e->user_data <= max_num)
        {
            res[doc_e->user_data] = doc_e->tag;
            doc_e->tag = NULL;
            colors[doc_e->user_data] = doc_e->type.u8[1] & 15;
        }
        doc_e = doc_e->next;
    }

    DocDel(doc);
    if (_max_num)
        *_max_num = max_num;
    if (_colors)
        *_colors = colors;
    else
        Free(colors);

    return res;
}

public U0 StrFileArrDel(U8 **a, I64 max_num)
{
    I64 i;

    for (i = 0; i <= max_num; i++)
        Free(a[i]);
    Free(a);
}

public I64 StrFileAdd(U8 *st, I64 *_num, CHashTable *table, I64 color=COLOR_INVALID)
{
    CHashGeneric *tmph;

    if (!st)
        return 0;
    if (!(tmph = HashFind(st, table, SFT_GENERIC)))
    {
        tmph = CAlloc(sizeof(CHashGeneric));
        tmph->type          = SFT_GENERIC;
        tmph->str           = StrNew(st);
        tmph->user_data0    = (*_num)++;
        HashAdd(tmph, table);
    }
    if (color != COLOR_INVALID)
        tmph->user_data1 = color;

    return tmph->user_data0;
}

I64 StrEntriesCompare(CHashGeneric *h1, CHashGeneric *h2)
{
    return h1->user_data0 - h2->user_data0;
}

public U0 StrFileWrite(U8 *name, CHashTable *table, Bool no_nums=FALSE)
{
    I64              i, j, count, color = BLACK;
    CDoc            *doc = DocNew(name);
    CHashGeneric    *tmph, **a;

    if (table)
    {
        count = 0;      //Count Strings
        for (i = 0; i <= table->mask; i++)
            count += LinkedListCount(table->body[i]);
        a = MAlloc(count * sizeof(CHashGeneric *));
        j = 0;              //Load Strings
        for (i = 0; i <= table->mask; i++)
        {
            tmph = table->body[i];
            while (tmph)
            {
                a[j++] = tmph;
                tmph = tmph->next;
            }
        }
        QuickSortI64(a, count, &StrEntriesCompare);
        for (i = 0; i < count; i++)
        {
            tmph = a[i];
            if (tmph->user_data1 & 15 != color)
            {
                DocPrint(doc, "$FG,%d$", tmph->user_data1 & 15);
                color = tmph->user_data1 & 15;
            }
            if (no_nums)
                DocPrint(doc, "%s\n", tmph->str);
            else
                DocPrint(doc, "%d,%s\n", tmph->user_data0, tmph->str);
        }
        Free(a);
    }
    doc->flags |= DOCF_NO_CURSOR;
    DocWrite(doc);
    DocDel(doc);
}

public U0 StrFileDel(CHashTable *table)
{
    I64           i;
    CHashGeneric *tmph, *tmph1;

    if (!table)
        return;
    for (i = 0; i <= table->mask; i++)
    {
        tmph = table->body[i];
        while (tmph)
        {
            tmph1=tmph->next;
            Free(tmph->str);
            Free(tmph);
            tmph = tmph1;
        }
    }
    Free(table->body);
    Free(table);
}