I64 HasLower(U8 *src)
{
        I64 ch;

        while (ch = *src++)
                if ('a' <= ch <= 'z')
                        return TRUE;
        return FALSE;
}

U0 HashFunSegFind(CHashTable *h, U8 *addr, Bool *_has_lower, U64 *_best, CHash **_res)
{
        Bool            *has_lower = *_has_lower;
        CHashExport     *tmpex;
        U64                      i, j, best = *_best;
        CHash           *res = *_res;

        for (i = 0; i <= h->mask; i++)
        {
                tmpex = h->body[i];
                while (tmpex)
                {
                        j = 0;
                        if (tmpex->type & HTT_FUN)
                        {
                                if (!Bt(&tmpex(CHashFun *)->flags, Cf_EXTERN) && !Bt(&tmpex(CHashFun *)->flags, Ff_INTERNAL))
                                        j = tmpex(CHashFun *)->exe_addr;
                        }
                        else if (tmpex->type & HTT_EXPORT_SYS_SYM)
                                j = tmpex->val;
                        if (j)
                        {
                                j = addr(I64) - j;
                                if (0 <= j <= best)
                                {
                                        if (tmpex->type & HTT_EXPORT_SYS_SYM)
                                        {
                                                if (j < best || j == best && !has_lower)
                                                {
                                                        has_lower = HasLower(tmpex->str);
                                                        best = j;
                                                        res = tmpex;
                                                }
                                        }
                                        else if (tmpex->type & HTT_FUN)
                                        {
                                                if (j < best || j == best && (res && res->type & HTT_EXPORT_SYS_SYM || !has_lower))
                                                {
                                                        has_lower = HasLower(tmpex->str);
                                                        best = j;
                                                        res = tmpex;
                                                }
                                        }
                                }
                        }
                        tmpex = tmpex->next;
                }
        }
        *_has_lower = has_lower;
        *_best = best;
        *_res  = res;
}

CHash *FunSegFind(U8 *addr, I64 *_offset)
{//See Hash.
        CHash           *res = NULL;
        Bool             has_lower = FALSE;
        CTask           *task;
        CHashTable      *h;
        CCPU            *c;
        U64                      i, best = 0xFFFF;

        if (!CheckCodePtr(addr))
        {
                *_offset = best;
                return NULL;
        }
        if (IsDebugMode)
                for (i = 0; i < mp_count; i++)
                {
                        c = &cpu_structs[i];
                        task = c->executive_task;
                        do
                        {
                                if (!TaskValidate(task))
                                        goto fs_abort_task;

                                h = task->hash_table;
                                while (h)
                                {
                                        HashFunSegFind(h, addr, &has_lower, &best, &res);
                                        h = h->next;
                                }
                                task = task->next_task;
                        }
                        while (task != c->executive_task);

fs_abort_task:
                }
        else
        {
                h = Fs->hash_table;
                while (h)
                {
                        HashFunSegFind(h, addr, &has_lower, &best, &res);
                        h = h->next;
                }
        }
        *_offset = best;

        return res;
}

U0 FunSegCacheAdd(CHash *tmps, U8 *addr)
{
        I64                              i;
        CDebugInfo              *debug_info;
        CFunSegCache    *tmpfsc;

        if (tmps && tmps->type & HTT_FUN && (debug_info = tmps(CHashFun *)->debug_info))
        {
                lock i = debug.fun_seg_cache_index++;
                tmpfsc = &debug.fun_seg_cache[i & (FUN_SEG_CACHE_SIZE - 1)];
                tmpfsc->base = debug_info->body[0];
                if (addr < tmpfsc->base)
                        tmpfsc->base = addr;
                tmpfsc->limit = debug_info->body[debug_info->max_line + 1 - debug_info->min_line];
                if (addr >= tmpfsc->limit)
                        tmpfsc->limit = addr + 1;
                i = MinI64(StrLen(tmps->str), FUN_SEG_CACHE_STR_LEN - 1);
                MemCopy(tmpfsc->str, tmps->str, i);
                tmpfsc->str[i] = 0;
                tmpfsc->time_stamp = tS;
        }
}

U8 *FunSegCacheFind(U8 *addr, I64 *_offset)
{
        I64                              i;
        F64                              timeout;
        CFunSegCache    *tmpfsc = debug.fun_seg_cache;

        if (addr == SYS_IDLE_PT)
        {
                *_offset = 0;
                return "SYS_IDLE_PT";
        }
        else
        {
                timeout = tS + 8.0;
                for (i = 0; i < FUN_SEG_CACHE_SIZE; i++, tmpfsc++)
                        if (tmpfsc->base <= addr < tmpfsc->limit && tmpfsc->time_stamp > timeout)
                        {
                                *_offset = addr - tmpfsc->base;
                                return tmpfsc->str;
                        }
                return NULL;
        }
}

U0 StrPrintFunSeg(U8 *buf, I64 addr, I64 field_len, I64 flags)
{
        I64                      offset;
        CHashExport     *tmpex;
        U8                      *str, *str2;
        Bool             is_fun = FALSE;

        if (!(flags & PRINTF_TRUNCATE))
                field_len = 0;
        if (addr)
        {
                if (str = FunSegCacheFind(addr, &offset))
                {
                        if (addr != SYS_IDLE_PT)
                                is_fun = TRUE;
                }
                else
                {
                        if (tmpex = FunSegFind(addr, &offset))
                        {
                                if (tmpex->type & HTT_FUN)
                                        is_fun = TRUE;
                                FunSegCacheAdd(tmpex, addr);
                                str = tmpex->str;
                        }
                }
                if (str)
                {
                        if (offset > 0xFFFF)
                                offset = 0xFFFF;
                        if (flags & PRINTF_COMMA)
                        {
                                if (is_fun)
                                {
                                        str2 = MStrPrint("&%s", str);
                                        if (!field_len)
                                                StrCopy(buf, str2);
                                        else if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str2) < field_len)
                                                StrCopy(buf, str2);
                                        else
                                                StrPrint(buf, "%*ts", field_len, str2);
                                        Free(str2);
                                }
                                else
                                {
                                        if (!field_len)
                                                StrCopy(buf, str);
                                        else if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str) < field_len)
                                                StrCopy(buf, str);
                                        else
                                                StrPrint(buf, "%*ts", field_len, str);
                                }
                        }
                        else
                        {
                                if (is_fun)
                                {
                                        str2 = MStrPrint("&%s", str);
                                        if (field_len && field_len > 7)
                                        {
                                                if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str2) < field_len - 7)
                                                        StrPrint(buf, "%s+0x%04X", str2, offset);
                                                else
                                                        StrPrint(buf, "%*ts+0x%04X", field_len - 7, str2, offset);
                                        }
                                        else
                                                StrPrint(buf, "%s+0x%04X", str2, offset);
                                        Free(str2);
                                }
                                else
                                {
                                        if (field_len && field_len > 7)
                                        {
                                                if (flags & PRINTF_LEFT_JUSTIFY && StrLen(str) < field_len - 7)
                                                        StrPrint(buf, "%s+0x%04X", str, offset);
                                                else
                                                        StrPrint(buf, "%*ts+0x%04X", field_len - 7, str, offset);
                                        }
                                        else
                                                StrPrint(buf, "%s+0x%04X", str, offset);
                                }
                        }
                        return;
                }
        }
        if (flags & PRINTF_COMMA)
                StrCopy(buf, ".");
        else if (flags & PRINTF_TRUNCATE && field_len)
                StrPrint(buf, "%*tX", field_len, addr);
        else
                StrPrint(buf, "%X", addr);
}

I64 SrcLineNum(U8 *addr, I64 count=1)
{//linenum for src of addr.
        CHashSrcSym     *tmph;
        I64                      cur_line, first_line, last_line, num_lines, offset;
        CDebugInfo      *debug_info;
        U32                     *body;
        U8                      *src, *src2;

        if (tmph = FunSegFind(addr, &offset))
        {
                if (tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM))
                {
                        if (debug_info = tmph->debug_info)
                        {
                                num_lines = debug_info->max_line - debug_info->min_line + 1;
                                body = debug_info->body;

                                //find first nonzero
                                first_line = 0;
                                while (!body[first_line])
                                {
                                        first_line++;
                                        if (first_line >= num_lines)
                                                return -1;
                                }

                                //find last nonzero
                                last_line = num_lines - 1;
                                while (!body[last_line] && last_line > first_line)
                                        last_line--;

                                        //interpolate to guess line num
                                cur_line = ClampI64(ToF64(addr - body[first_line]) * (last_line - first_line + 1) /
                                                        (body[last_line] - body[first_line] + 1),
                                                        first_line, last_line);

                                //retreat while too high
                                while ((!body[cur_line] || body[cur_line] >= addr) && cur_line > first_line)
                                        cur_line--;

                                        //advance while to low
                                while ((!body[cur_line] || body[cur_line] < addr) && cur_line < last_line)
                                        cur_line++;

                                if (addr < body[cur_line] + count)
                                        return cur_line + debug_info->min_line;

                        }
                        else if (tmph->src_link)
                        {
                                src  = StrNew(tmph->src_link);
                                src2 = StrNew(tmph->src_link);
                                StrLastRemove(src, ",", src2);
                                cur_line = Str2I64(src2);
                                Free(src);
                                Free(src2);
                                return cur_line;
                        }
                }
        }

        return -1;
}

U8 *SrcFileName(U8 *addr, I64 count=1, CTask *mem_task=NULL)
{//MAlloc filename for src of addr.
        CHashSrcSym     *tmph;
        I64                      i, j, ii, offset, best = NULL, d, best_d;
        U32                     *body;
        CDebugInfo      *debug_info;
        U8                      *src;

        if ((tmph = FunSegFind(addr, &offset)) && tmph->type & (HTT_FUN | HTT_EXPORT_SYS_SYM))
        {
                if (debug_info = tmph->debug_info)
                {
                        j = debug_info->max_line - debug_info->min_line + 1;
                        body = debug_info->body;
                        best_d = I64_MAX;
                        for (i = 0; i < j; i++)
                        {
                                if (0 < body[i] <= addr < body[i] + count)
                                {
                                        ii = i + 1;
                                        while (!body[ii])
                                                ii++;
                                        if (addr < body[ii])
                                        {
                                                d = addr(I64) - body[i];
                                                if (d < best_d)
                                                {
                                                        best_d = d;
                                                        best = tmph->src_link;
                                                }
                                        }
                                }
                        }
                }
                else
                        best = tmph->src_link;
        }
        if (best)
        {
                src = StrNew(best, mem_task);
                StrFirstRemove(src, ":");
                StrLastRemove(src, ",");
                return src;
        }
        else
                return NULL;
}

U8 *SrcEdLink(U8 *addr, I64 count=1, CTask *mem_task=NULL)
{//MAlloc file,line link to src of addr.
        U8 *filename, *st, *st2;
        I64 linenum;

        if (filename = SrcFileName(addr, count))
        {
                linenum = SrcLineNum(addr, count);
                if (linenum < 1)
                        linenum = 1;
                st2 = MStrPrint("FL:%s,%d", filename, linenum);
                Free(filename);
                st = StrNew(st2, mem_task);
                Free(st2);
                return st;
        }

        return NULL;
}

Bool PutSrcLink(U8 *addr, I64 count=1, U8 *buf=NULL)
{//Put to StdOut a DolDoc file,line link to src of addr.
        U8 *src;

        if (src = SrcEdLink(addr, count))
        {
                if (buf)
                        StrPrint(buf, "$LK,\"%p\",A=\"%s\"$", addr, src);
                else
                        "$LK,\"%p\",A=\"%s\"$", addr, src;
                Free(src);
                return TRUE;
        }
        else if (buf)
                *buf = 0;

        return FALSE;
}

Bool E(U8 *addr, I64 count=512, I64 edf_dof_flags=0)
{//Edit src at addr.
        U8      *st;
        Bool res = FALSE;

        if (st = SrcEdLink(addr, count))
        {
                if (IsRaw)
                        res = EdLiteFileLine(st, edf_dof_flags);
                else
                        res = Ed(st, edf_dof_flags);
                Free(st);
        }

        return res;
}

Bool Man(U8 *st, I64 edf_dof_flags=0)
{//Owner's manual for symbol.  Edit src code for symbol.
        Bool              res = FALSE;
        U8                      **st2;
        CHashSrcSym      *tmph;

        if (IsRaw)
        {
                if ((tmph = HashFind(st, Fs->hash_table, HTG_SRC_SYM)) && tmph->src_link)
                        res = EdLiteFileLine(tmph->src_link, edf_dof_flags);
        }
        else
        {
                st2 = MStrPrint("MN:%s", st);
                res = Ed(st2, edf_dof_flags);
                Free(st2);
        }

        return res;
}