#define JSONT_INVALID   0
#define JSONT_STRING    1
#define JSONT_INTEGER   2
#define JSONT_FLOAT     3
#define JSONT_ARRAY     4
#define JSONT_BOOL      5
#define JSONT_OBJ       6
#define JSONT_NULL      7

#define JSON_HASHTABLE_SIZE 1024

#define HTT_JSON    0x00100 //identical to HTT_DICT_WORD

class CJSONDataEntry:CQueue
{
    U8               type;

    I64              int_data;
    F64              float_data;
    U8              *string_data;
    Bool             bool_data;
    CJSONDataEntry  *list_data;
    CHashTable      *hash_table;
};

class CJSONDataHash:CHash
{
    CJSONDataEntry  *data;
};

U8 **JSONGetKeys(CHashTable *table)
{
    I64       i, count = 0;
    CHash    *temp_hash;
    U8      **keys;
    I64       key_index = 0;

    for (i = 0; i <= table->mask; i++) //mask is table length 0-based
        if (temp_hash = table->body[i]) //if temp_hash exists
            count++;

    keys = CAlloc(sizeof(U8*) * count); // alloc string list
    for (i = 0; i <= table->mask; i++)
        if (temp_hash = table->body[i])
        {
            keys[key_index] = StrNew(temp_hash->str);//add key string to list
            key_index++;
        }

    return keys;
}

U0 JSONIndentLine(I64 indent)
{
    I64 i;
//  for (i = 0; i < indent; i++) {"  ";}
    for (i = 0; i < indent; i++) {" ";}

}

U0 JSONDataRep(CJSONDataEntry *data, I64 indent=0)
{
    U8              **keys;// = JSONGetKeys(data->hash_table);
    I64               index;
    I64               count;// = MSize(keys) / sizeof(U8*);
    CJSONDataEntry   *entry;// = data->list_data->next;//one after head.
    CJSONDataHash    *temp_hash;

    JSONIndentLine(indent);

    switch (data->type)
    {
        case JSONT_INVALID:
            "Invalid JSON.\n";
            break;

        case JSONT_STRING:
            "%s\n", data->string_data;
            break;

        case JSONT_INTEGER:
            "%d\n", data->int_data;
            break;

        case JSONT_FLOAT:
            "%.9f\n", data->float_data;
            break;

        case JSONT_BOOL:
            "%Z\n", data->bool_data, "ST_FALSE_TRUE";
            break;

        case JSONT_NULL:
            "Null.\n";
            break;

        case JSONT_ARRAY:
            "Array:\n";
            JSONIndentLine(indent);
            "[\n";
            entry = data->list_data->next;//one after head.
            while (entry != data->list_data)//head ignored, stop on head.
            {
                JSONDataRep(entry, indent + 1); // recursive Rep on the list entry
                entry = entry->next;
            }
            JSONIndentLine(indent);
            "]\n";
            break;

        case JSONT_OBJ:
            "Object.\n";
            JSONIndentLine(indent);
            "{\n";

            keys = JSONGetKeys(data->hash_table);
            count = MSize(keys) / sizeof(U8*);

            for (index = 0; index < count; index++)
            {
                JSONIndentLine(indent);
                "Key: %s\n", keys[index];
                temp_hash = HashFind(keys[index], data->hash_table, HTT_JSON);
                JSONDataRep(temp_hash->data, indent + 1);
            }
            JSONIndentLine(indent);
            "}\n";
            break;
    }
}

CJSONDataEntry *JSONParse(CCompCtrl *cc)
{
    CJSONDataEntry  *result = CAlloc(sizeof(CJSONDataEntry));
    I64              tk, last_tk;
    Bool             is_done = FALSE;
    CJSONDataEntry  *temp_entry;// = JSONParse(cc);
    CJSONDataHash   *temp_hash = CAlloc(sizeof(CJSONDataHash));

    while (tk = Lex(cc))
    {
//      ClassRep(cc);
        switch (tk)
        {

            case '}':
                LexExcept(cc, "Expected Value, got '}'.");
            case TK_STR:
                result->type        = JSONT_STRING;
                result->string_data = StrNew(cc->cur_str);

                is_done = TRUE;
                break;

            case TK_I64: //todo, LexExcept on 0x or 0b vals.
                result->type     = JSONT_INTEGER;
                result->int_data = cc->cur_i64;

//              LexPush(cc);
//              "got hex val, token string is %s\n",cc->cur_str;
//              ClassRep(cc);
//              Break;

                is_done = TRUE;
                break;

            case TK_F64:
                result->type        = JSONT_FLOAT;
                result->float_data  = cc->cur_f64;

                is_done = TRUE;
                break;

            case TK_IDENT:
                if (!StrCompare(cc->cur_str, "true") ||
                    !StrCompare(cc->cur_str, "false"))
                {
                    result->type = JSONT_BOOL;

                    if (!StrCompare(cc->cur_str, "true"))
                        result->bool_data = TRUE;
                    if (!StrCompare(cc->cur_str, "false"))
                        result->bool_data = FALSE;

                    is_done = TRUE;
                }
                if (!StrCompare(cc->cur_str, "null"))
                {
                    result->type = JSONT_NULL;

                    is_done = TRUE;
                }

                is_done = TRUE;
                break;

            case '[':
                result->type      = JSONT_ARRAY;
                result->list_data = CAlloc(sizeof(CJSONDataEntry));
                QueueInit(result->list_data);

lex_listitem:
/*
                CCompCtrl* temp_cc = NULL;
                MemCopy(temp_cc, cc, CompCtrlSize(cc));
                tk = Lex(temp_cc);
//              CompCtrlDel(temp_cc);
*/
                last_tk = tk;
                LexPush(cc);
                tk = Lex(cc);

                if (last_tk == ',' && tk == ']')
                    LexExcept(cc, "Expected List value, got ']'");

                if (tk == ']')
                    goto lex_listdone;

                if (tk == ',')
                    LexExcept(cc, "Expected List Value, got comma.");

                LexPopRestore(cc);
                temp_entry = JSONParse(cc);
                QueueInsert(temp_entry, result->list_data->last);

                tk = Lex(cc);
                if (tk == ',')
                    goto lex_listitem;

lex_listdone:

//              LexPopNoRestore(cc);
                is_done = TRUE;
                break;

            case '{':
                result->type        = JSONT_OBJ;
                result->hash_table  = HashTableNew(JSON_HASHTABLE_SIZE);

lex_objkey:
                //lex next. expect TK_STR. Make a temp_hash.
                last_tk = tk;
                tk = Lex(cc);

                if (last_tk == ',' && tk == '}')
                    LexExcept(cc, "Expected Key after comma.");

                if (tk == '}')
                    goto lex_objdone;

                if (tk != TK_STR)
                    LexExcept(cc, "Expected Key String.");

                temp_hash = CAlloc(sizeof(CJSONDataHash));

                //set hash type and StrNew with cc->cur_str into hash str.
                temp_hash->type = HTT_JSON;
                temp_hash->str  = StrNew(cc->cur_str);

                //lex next. expect ':'. 
                tk = Lex(cc);
                if (tk != ':')
                    LexExcept(cc, "Expected ':' after Key String.");

                //now expect JSONDataEntry-able value next.
                //Recursive JSONParse into hash data member.
                temp_hash->data = JSONParse(cc);

                //JSONParse leaves off on the last token. e.g. int, tk will
                //still be TK_I64.

                //add hash to result hash_table.
                HashAdd(temp_hash, result->hash_table);
//              "Debugging.\nHash added with str:%s.\n",temp_hash->str;
//              ClassRep(temp_hash->data);

                //lex next. expect ',' or '}'.
                tk = Lex(cc);
                if (tk != ',' && tk != '}')
                    LexExcept(cc, "Expected ',' or '}' after Object Value.");
                //if ',' ... Terry's parsing code would imply labels and gotos.
                //i wonder if there's a better, less BASIC way.
                if (tk == ',')
                    goto lex_objkey;
lex_objdone:

                is_done = TRUE;
                break;
        }

        if (is_done)
            break;
    }

    return result;
}

U0 Test(U8 *filename)
{
    CCompCtrl       *cc     = CompCtrlNew(MStrPrint("#include \"%s\"", filename));
    CJSONDataEntry  *result = JSONParse(cc);

    JSONDataRep(result);
}

Cd(__DIR__);
Test("JSON1.TXT");