#help_index "Help System"

U8 *KeyMapKeyMStrPrint(I64 sc, U0 (*fp_handler)(I64 sc), U8 *desc, CTask *task = NULL)
{
    I64          i = 9, k, c;
    U8          *st, *st2, *res, *ptr;
    CHashTable  *old_hash = Fs->hash_table;

    st = ScanCode2KeyName(sc);
    if (sc & SCF_CTRL)                   i += 5;
    if (sc & SCF_ALT)                    i += 4;
    if (sc & (SCF_SHIFT | SCF_NO_SHIFT)) i += 6;
    if (TaskValidate(task))
        Fs->hash_table = task->hash_table;
    st2 = SrcEdLink(fp_handler, 256);
    Fs->hash_table = old_hash;

    k = *desc(U32 *);
    if (k == 'Edit')        c = BLUE;
    else if (k == 'Dol ')   c = GREEN;
    else if (k == 'Cmd ')   c = RED;
    else                    c = BLACK;

    res = MStrPrint("%-*s $FG,%d$$TX+UL+L+PU,\"%$Q\",A=\"%s\"$$FG$\n", i, st, c, desc, st2);
    Free(st);
    Free(st2);

    ptr = res;
    while (*ptr)
        ptr++;
    

    return res;
}

U0 KeyMapKeyPrint(I64 sc, U0 (*fp_handler)(I64 sc), U8 *desc, CTask *task = NULL)
{
    U8 *st = KeyMapKeyMStrPrint(sc, fp_handler, desc, task);

    "%s", st;
    Free(st);
}

U0 KeyMapCtrlAltFamily(Bool no_shift, Bool shift)
{
    I64 i, no_shift_f;

    if (no_shift && shift)
        no_shift_f = SCF_NO_SHIFT;
    else
        no_shift_f = 0;
    if (no_shift)
        KeyMapKeyPrint(SC_DELETE + SCF_CTRL + SCF_ALT + no_shift_f, &Reboot, "Cmd /Reboot");
    if (no_shift)
        KeyMapKeyPrint(SC_ESC + SCF_CTRL + SCF_ALT + no_shift_f, &User, "Cmd /Terminal Window");
    if (no_shift)
        KeyMapKeyPrint(SC_TAB + SCF_CTRL + SCF_ALT + no_shift_f, &WinToTop, "Cmd /Next Focus Task");

    for (i = 0; i < 26; i++)
        if (keydev.fp_ctrl_alt_cbs[i])
        {
            if (no_shift && keydev.ctrl_alt_no_shift_descs[i])
                KeyMapKeyPrint(Char2ScanCode(i + 'a') + SCF_CTRL + SCF_ALT + no_shift_f,
                            keydev.fp_ctrl_alt_cbs[i], keydev.ctrl_alt_no_shift_descs[i]);
            if (shift && keydev.ctrl_alt_shift_descs[i])
                KeyMapKeyPrint(Char2ScanCode(i + 'a') + SCF_CTRL + SCF_ALT + SCF_SHIFT,
                            keydev.fp_ctrl_alt_cbs[i], keydev.ctrl_alt_shift_descs[i]);
        }
}

U0 KMComparePrepare(U8 *buf, I64 *src)
{
    I64 i, *dst = buf;
    U8 *ptr;

    if (src)
    {
        *dst++ = *src++;
        *dst++ = *src++;
        *dst++ = *src++;
        *dst++ = *src++;
        *dst(U8 *) = 0;
        if (ptr = StrMatch("SHIFT", buf))
        {
            for (i = 0; i < 5; i++)
                ptr[i] = CH_SPACE;
            if (ptr = StrMatch("$", buf))
                *ptr = 255;
        }
    }
    else
        *buf=0;
}

I64 KMCompare(U8 *e1, U8 *e2)
{
    U8 buf1[STR_LEN], buf2[STR_LEN];

    KMComparePrepare(buf1, e1);
    KMComparePrepare(buf2, e2);

    return StrCompare(buf1, buf2);
}

U0 KeyMapFamily2(U8 **entries, CTask *task, I64 scf)
{
    I64 i, arg1, arg2;

    for (i = 0; i < 256; i++)
    {
        arg2 = scf | i | SCF_KEY_DESC;
        arg1 = ScanCode2Char(arg2);
        *keydev.desc = 0;
        keydev.handler = NULL;
        if (TaskValidate(task) && !Bt(&task->win_inhibit, WIf_SELF_KEY_DESC))
        {
            if (task == Fs)
                PutKey(arg1, arg2);
            else
                MessagePost(task, MESSAGE_KEY_DOWN, arg1, arg2);
            Refresh(0, TRUE);
            Sleep(1); //Open loop because might be no response.  TODO: Drops messages.
        }
        if (*keydev.desc && StrNCompare(keydev.desc, "Char  /",7))
            entries[i] = KeyMapKeyMStrPrint(arg2, keydev.handler, keydev.desc, task);
    }
}

U0 KeyMapFamily(CTask *task, I64 scf, Bool no_shift, Bool shift)
{
    I64  i, count=0;
    U8 **entries = CAlloc(2 * 256 * sizeof(U8 *)), **ptr = entries;

    if (no_shift)
    {
        if (shift)
            KeyMapFamily2(ptr, task, scf + SCF_NO_SHIFT);
        else
            KeyMapFamily2(ptr, task, scf);
        ptr += 256;
        count += 256;
    }
    if (shift)
    {
        KeyMapFamily2(ptr, task, scf + SCF_SHIFT);
        ptr += 256;
        count += 256;
    }
    QuickSortI64(entries,count, &KMCompare);
    for (i = 0;i < count; i++)
        if (entries[i])
        {
            "%s", entries[i];
            Free(entries[i]);
        }
    Free(entries);
}

public U0 KeyMap(CTask *task = NULL)
{//Report desc of all keys.
    Bool old_key_desc;
    if (!task) task = Fs;
    old_key_desc = LBtr(&task->win_inhibit, WIf_SELF_KEY_DESC);
    DocMax;
    KeyMapFamily(task, 0, TRUE, TRUE);
    KeyMapFamily(task, SCF_CTRL, TRUE, TRUE);
    KeyMapFamily(task, SCF_ALT, TRUE, TRUE);
    KeyMapCtrlAltFamily(TRUE, TRUE);
    LBEqual(&task->win_inhibit, WIf_SELF_KEY_DESC, old_key_desc);
    "\nKeyMap Completed.\n";
}

#help_index "Help System/Training"
public U0 TipOfDay(U8 *tip_file = "::/Doc/Tips.DD")
{//Print random tip-of-day from ::/Doc/Tips.DD.
    I64          i = RandU16;
    CDoc        *doc = DocRead(tip_file), *doc2 = DocNew;
    CDocEntry   *doc_e = doc->head.next;

    "$WW,1$\n";
    while (TRUE)
    {
        if (doc_e->type_u8 == DOCT_TEXT && *doc_e->tag == '*')
            if (!i--) break;
        doc_e = doc_e->next;
    }
    if (doc_e->type_u8 == DOCT_TEXT && *doc_e->tag == '*')
    {
        while (doc_e != doc)
        {
            if (doc_e->type_u8 != DOCT_ERROR)
                DocInsEntry(doc2, DocEntryCopy(doc2, doc_e));
            doc_e = doc_e->next;
            if (doc_e->type_u8 == DOCT_TEXT && *doc_e->tag == '*')
                break;
        }
    }
    DocInsDoc(DocPut, doc2);
    DocDel(doc2);
    DocDel(doc);
}