#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);
}