#help_index "Info;Hash/System;Cmd Line (Typically)"
class CWho
{
	CHashGeneric *h;
	U8 *idx;
};

I64 HashEntriesCompare(CWho *h1, CWho *h2)
{
	I64 i1, i2;

	if (i1 = StrCompare(h1->h->str, h2->h->str))
		return i1;
	i1 = HashTypeNum(h1->h);
	i2 = HashTypeNum(h2->h);

	return i1 - i2;
}

I64 HashEntriesCompare2(CWho *h1, CWho *h2)
{
	CHashFun	*tmpf1 = h1->h, *tmpf2 = h2->h;
	I64			 i1 = HashVal(tmpf1), i2 = HashVal(tmpf2);

	if (i1 == i2)
	{
		i1 = HashTypeNum(tmpf1);
		i2 = HashTypeNum(tmpf2);
		if (i1 == i2)
			return StrCompare(tmpf1->str, tmpf2->str);
	}

	return i1 - i2;
}

I64 HelpIndexCount(U8 *ptr, U8 *idx)
{
	I64 count = 0, ch, idx_len = StrLen(idx);

	while (*ptr)
	{
		if (!StrNCompare(ptr, idx, idx_len))
			count++;
		while (ch = *ptr++)
			if (ch == ';')
				break;
		if (!ch)
			ptr--;
	}

	return count;
}

U8 *HelpIndexStr(U8 **_ptr, U8 *idx)
{
	U8 *ptr = *_ptr, *ptr2, *res;
	I64 ch, idx_len = StrLen(idx);

	while (*ptr)
	{
		ptr2 = ptr;
		while (ch = *ptr++)
			if (ch == ';')
				break;
		if (!ch)
			ptr--;
		*_ptr = ptr;
		if (!StrNCompare(ptr2, idx, idx_len))
		{
			if (ch == ';')
				ptr--;
			*ptr = 0;
			res = StrNew(ptr2);
			*ptr = ch;
			return res;
		}
	}

	return NULL;
}

U8 *HelpComment(CTask *task, CHash *tmph, U8 *_src_link)
{
	CDoc		*doc;
	CDocEntry	*doc_e;
	U8			*res = NULL, *ptr, *ptr2, *src_link = StrNew(_src_link);

	if (*src_link == 'F' && src_link[2] == ':')
		*src_link = 'P';
	XTalkWait(task, "Ed(0x%X, DOF_DONT_WINMGR_SYNC | DOF_DONT_SHOW);\n", src_link);
	Free(src_link);

	doc = DocPut(task);
	doc_e = doc->cur_entry;
	if (tmph->type & HTT_FUN)
	{
		if (Bt(&tmph(CHashFun *)->flags, Ff__EXTERN) || Bt(&tmph(CHashFun *)->flags, Ff_INTERNAL))
			while (doc_e != doc && (!(doc_e->de_flags & DOCEF_TAG) || !StrOcc(doc_e->tag, ';')))
				doc_e = doc_e->next;
		else
			while (doc_e != doc && (!(doc_e->de_flags & DOCEF_TAG) || !StrOcc(doc_e->tag, '{')))
				doc_e = doc_e->next;
	}
	if (doc_e != doc)
	{
		if (doc_e->de_flags & DOCEF_TAG)
		{
			ptr = doc_e->tag;
			if (ptr2 = StrMatch("//", ptr))
				ptr = ptr2 + 2;
			else if (ptr2 = StrMatch("/*", ptr))
				ptr = ptr2 + 2;
			else if (!StrNCompare(ptr, "public", 6))
				ptr += 6;
			while (*ptr == CH_SPACE)
				ptr++;
			res = StrNew(ptr);
			doc_e = doc_e->next;
		}
		while (doc_e != doc && doc_e->type_u8 != DOCT_NEW_LINE)
		{
			if (doc_e->type_u8 == DOCT_TAB)
			{
				ptr = MStrPrint("%s ", res);
				Free(res);
				res = ptr;
			}
			else if (doc_e->de_flags & DOCEF_TAG)
			{
				ptr = MStrPrint("%s%s", res, doc_e->tag);
				Free(res);
				res = ptr;
			}
			doc_e = doc_e->next;
		}
	}
	XTalkWait(task, "%c", CH_SHIFT_ESC);
	if (res)
	{
		ptr = MStrUtil(res, SUF_REM_TRAILING | SUF_REM_LEADING | SUF_SINGLE_SPACE);
		Free(res);
		res = ptr;
	}

	return res;
}

I64 HashEntriesCompare3(CWho *h1, CWho *h2)
{
	I64 i, i1 = 0, i2 = 0;

	i = StrCompare(h1->idx, h2->idx);
	if (i)
		return i;
	else
	{
		if (h1->h->type & HTT_HELP_FILE)
			i1 = 1;
		if (h2->h->type & HTT_HELP_FILE)
			i2 = 1;
		i = i2 - i1;
		if (i)
			return i;
		else
			return StrCompare(h1->h->str, h2->h->str);
	}
}

public U0 Who(U8 *fu_flags=NULL, CHashTable *h=NULL, U8 *idx=NULL, CDoc *doc=NULL)
{	//Dump hash symbol table.
	// "+p" for only public symbols
	// "+m" to order by number (normally alphabetical)
	// "-r" just local hash table
	CHashTable		*table;
	CHashSrcSym		*tmph;
	CHashGeneric	*ptr;
	CWho			*list;
	I64				 count, i, j, k, f = 0;
	U8				 buf[512], *last_idx = StrNew(""), *cur_idx, *comment;
	Bool			 recurse, publics, map;
	CTask			*task;

	FlagsScan(&f, Define("ST_FILE_UTIL_FLAGS"), "+r");
	FlagsScan(&f, Define("ST_FILE_UTIL_FLAGS"), fu_flags);
	if (f & ~(FUF_RECURSE | FUF_PUBLIC | FUF_MAP))
		throw('FUF');
	recurse = Bt(&f, FUf_RECURSE);
	publics = Bt(&f, FUf_PUBLIC);
	map 	= Bt(&f, FUf_MAP);

	if (!h)
		h = Fs->hash_table;

	if (idx)
	{
		task = User;
		TaskWait(task);
		LBtr(&task->display_flags, DISPLAYf_SHOW);
	}
	else
		task = NULL;

	count = 0;
	table = h;
	while (table)
	{
		for (i = 0; i <= table->mask; i++)
		{
			tmph = table->body[i];
			while (tmph)
			{
				if (!(tmph->type & (HTF_IMPORT | HTF_PRIVATE)) && (tmph->type & HTF_PUBLIC || !publics))
				{
					if (!idx)
						count++;
					else if (tmph->type & HTG_SRC_SYM && (cur_idx = tmph->idx))
						count += HelpIndexCount(cur_idx, idx);
				}
				tmph = tmph->next;
			}
		}
		if (recurse)
			table = table->next;
		else
			break;
	}
	if (!count) goto wh_done;

	list = CAlloc(count * sizeof(CWho));
	j = 0;
	table = h;
	while (table)
	{
		for (i = 0; i <= table->mask; i++)
		{
			tmph = table->body[i];
			while (tmph)
			{
				if (!(tmph->type & (HTF_IMPORT | HTF_PRIVATE)) && (tmph->type & HTF_PUBLIC || !publics))
					if (!idx)
						list[j++].h = tmph;
					else if (tmph->type & HTG_SRC_SYM && (cur_idx=tmph->idx) && (k = HelpIndexCount(cur_idx, idx)))
						while (k--)
						{
							list[j].idx = HelpIndexStr(&cur_idx, idx);
							list[j++].h = tmph;
						}
				tmph = tmph->next;
			}
		}
		if (recurse)
			table = table->next;
		else
			break;
	}

	if (map)
		QuickSort(list, count, sizeof(CWho), &HashEntriesCompare2);
	else if (idx)
		QuickSort(list, count, sizeof(CWho), &HashEntriesCompare3);
	else
		QuickSort(list, count, sizeof(CWho), &HashEntriesCompare);

	if (idx)
	{
		progress1_max = count;
		progress1 = 0;
	}
	for (i = 0; i < count; i++)
	{
		comment = NULL;
		ptr = list[i].h;
		if (idx)
			if (cur_idx = list[i].idx)
			{
				if (StrCompare(cur_idx, last_idx))
				{
					Free(last_idx);
					last_idx = StrNew(cur_idx);
					if (i)
						DocPrint(doc, "\n\n");
					DocPrint(doc, "$$WW,0$$$$PURPLE$$$$TX+CX,\"%$$Q\"$$$$FG$$\n", cur_idx);
				}
			}

		if (idx && ptr->type & HTT_HELP_FILE)
		{
			DocPrint(doc, "$$WW,1$$");
			DocType(doc, ptr->str);
			DocPrint(doc, "$$WW,0$$");
		}
		else
		{
			if (ptr->type & HTG_SRC_SYM && ptr(CHashSrcSym *)->src_link)
			{
				DocPrint(doc, "$$LK,\"%-20s\",A=\"%s\"$$", ptr->str, ptr(CHashSrcSym *)->src_link);
				if (idx)
					comment = HelpComment(task, ptr, ptr(CHashSrcSym *)->src_link);
			}
			else
				DocPrint(doc, "%-20s", ptr->str);

			if (!idx)
			{
				if (ptr->type & HTT_DEFINE_STR)
				{
					j = ptr(CHashDefineStr *)->count;
					if (j == -1)
						StrPrint(buf, "%-10t$$Q   ", ptr(CHashDefineStr *)->data);
					else
						StrPrint(buf, "%-10t$$Q %02X", ptr(CHashDefineStr *)->data, j);
				}
				else if (ptr->type & HTT_GLOBAL_VAR)
					StrPrint(buf, "%010X   ", ptr(CHashGlobalVar *)->data_addr);
				else
					StrPrint(buf, "%010X   ", HashVal(ptr));
				j = HashEntrySize(ptr);
				if (j == -1)
					CatPrint(buf, " %04X            ", ptr->use_count);
				else
					CatPrint(buf, " %04X %010X ", ptr->use_count, j);
			}
			else
				*buf = 0;

			k = ptr->type;
			if (publics)
				k &= ~HTF_PUBLIC;
			if (!(k & HTG_TYPE_MASK))
				CatPrint(buf, "NULL ");
			while (k)
			{
				j = Bsf(k);
				if (j < 0)
					break;
				Btr(&k, j);
				CatPrint(buf, "%Z ", j, "ST_HTT_TYPES");
			}
			DocPrint(doc, "%s", buf);
			if (comment)
			{
				DocPrint(doc, "$$GREEN$$%s$$FG$$", comment);
				Free(comment);
			}
			DocPrint(doc, "\n");
		}
		Free(list[i].idx);
		if (idx)
			progress1++;
	}
	Free(list);
	if (idx)
		progress1 = progress1_max = 0;

wh_done:
	if (doc)
	{
		if (doc->head.next == doc)
			DocPrint(doc, "No Match");
		else
			DocRecalc(doc);
	}
	Free(last_idx);
	Kill(task);
}

#help_index "Info;Hash;Cmd Line (Typically)"

#define HDR_NUM 16
public I64 HashDepthRep(CHashTable *table=NULL)
{	//Hash table linked-list chain depth report.
	//Histogram of collision count.
	I64		 i, j, longest = 0, count = 0, a[HDR_NUM];
	CHash	*tmph;

	if (!table)
		table = Fs->hash_table;
	MemSet(a, 0, sizeof(a));
	for (i = 0; i <= table->mask; i++)
	{
		tmph = table->body[i];
		if (tmph)
		{
			j = LinkedListCount(tmph);
			if (j < HDR_NUM)
				a[j]++;
			count += j;
			if (j > longest)
				longest = j;
		}
	}
	"Histogram\n";
	for (i = 0; i < HDR_NUM; i++)
		if (a[i])
			"%02d:%d\n", i, a[i];
	"Size:%d Count:%d Longest:%d\n", table->mask + 1, count, longest;

	return longest;
}

#help_index "Help System"
#help_file "::/Doc/HelpSystem"

public U0 DocHelpIdx(CDoc *doc, U8 *idx)
{//Put to doc report for given help idx.
	Who("+p",, idx, doc);
}

public U0 PopUpHelpIndex(U8 *idx, CTask *parent=NULL)
{//PopUp win report for given help idx.
	U8 *buf;

	buf = MStrPrint("DocHelpIdx(DocPut, \"%s\"); View;", idx);
	PopUp(buf, parent);
	Free(buf);
}

#help_index "Hash/System"
public U0 MapFileLoad(U8 *filename)
{//Load map file so we have src line info.
	U8			*st, *ptr, *name = ExtDefault(filename, "MAP"), *absname = FileNameAbs(name);
	CDoc		*doc = DocRead(name);
	CDocEntry	*doc_e;
	CHashSrcSym	*tmph;
	I64			 i, j, base=0;
	CDebugInfo	*debug_info;

	FileExtRemove(absname);
	if (absname[1] == ':' && StrLen(absname) > 2 && (tmph = HashSingleTableFind(absname + 2, Fs->hash_table, HTT_MODULE)))
		base = tmph(CHashGeneric *)->user_data0 + sizeof(CBinFile);

	if (!doc) return;
	doc_e = doc->head.next;
	while (doc_e != doc)
	{
		if (doc_e->type_u8 == DOCT_LINK)
		{
			if (*doc_e->tag)
				st = MStrUtil(doc_e->tag, SUF_REM_TRAILING);
			else
				st = MStrUtil(doc_e->aux_str, SUF_REM_TRAILING);
			if (tmph = HashSingleTableFind(st, Fs->hash_table, HTG_SRC_SYM))
			{
				if (*doc_e->tag)
				{
					Free(tmph->src_link);
					tmph->src_link = doc_e->aux_str;
					ptr = tmph->src_link;
					if (ptr[0] && ptr[1] && ptr[2] == ':')
					{
						if (ptr[3] == ':')
							ptr[3] = blkdev.boot_drive_let;
						else if (ptr[3] == '~')
							ptr[3] = *blkdev.home_dir;
					}
					doc_e->aux_str = NULL;
				}
				if (tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM) &&
					!(debug_info = tmph->debug_info) && doc_e->bin_data &&
					(debug_info = doc_e->bin_data->data))
				{
					if (doc_e->bin_data->size > MSize(debug_info))
						"Corrupt Map Entry\n";
					else
					{
						doc_e->bin_data->data = NULL;
						tmph->debug_info = debug_info;
						for (i = debug_info->min_line; i <= debug_info->max_line + 1; i++)
						{
							j = i - debug_info->min_line;
							if (debug_info->body[j])
								debug_info->body[j] = debug_info->body[j] + base;
						}
					}
				}
			}
			Free(st);
		}
		doc_e = doc_e->next;
	}
	DocDel(doc);
	Free(name);
	Free(absname);
}
