#help_index "DolDoc/Link"

/* See ZealOS Link Types.
"filename"
"FI:filename"
"FA:haystack_filename,needle_anchor_str"
"FF:haystack_filename,needle_str"
"FF:haystack_filename,needle_str:occurnum"
"FL:filename,linenum"
"MN:SymName"
"PI:filename"
"PF:haystack_filename,needle_str"
"PF:haystack_filename,needle_str:occurnum"
"PL:filename,linenum"
"BF:haystack_bible_book,needle_str"
"DN:word"
"DN:word,defnum"
"HI:index"
"AD:code_address_number"

To edit a doc structure already in memory. See SpriteEdText().
"AI:doc_address"
"AA:haystack_doc_address,needle_anchor_str"
"AF:haystack_doc_address,needle_str"
"AF:haystack_doc_address,needle_str:occurnum"
"AL:doc_address,linenum"
*/

#define LK_FILE         0
#define LK_FILE_ANCHOR  1
#define LK_FILE_FIND    2
#define LK_FILE_LINE    3
#define LK_MAN_PAGE     4
#define LK_PLAIN        5
#define LK_PLAIN_FIND   6
#define LK_PLAIN_LINE   7
#define LK_BIBLE_FIND   8
#define LK_DEF          9
#define LK_HELP_INDEX   10
#define LK_ADDR         11
#define LK_DOC          12 //See SpriteEdText()
#define LK_DOC_ANCHOR   13
#define LK_DOC_FIND     14
#define LK_DOC_LINE     15
#define LK_PLACE_ANCHOR 16

public U8 *DocEntryLink(CDoc *doc, CDocEntry *doc_e)
{//MAlloc new str, either tag or aux_str if link.
    if (doc_e->de_flags & DOCEF_LINK)
    {
        if (doc_e->de_flags & DOCEF_AUX_STR)
            return StrNew(doc_e->aux_str, doc->mem_task);
        else if (doc_e->de_flags & DOCEF_TAG)
            return StrNew(doc_e->tag, doc->mem_task);
    }

    return NULL;
}

Bool DocFileEd(I64 _type, U8 *filename, U8 *needle_str, I64 *_num, I64 edf_dof_flags)
{
    I64          type = _type, flags = 0, old_border_src = Fs->border_src;
    CDocEntry   *doc_e;
    CDoc        *doc;
    Bool         old_silent = Bt(&Fs->display_flags, DISPLAYf_SILENT), res = FALSE, other_found = FALSE;
    U8          *st1, *st2;

    try
    {
        switch (type)
        {
            case LK_PLAIN:
                flags = DOCF_PLAIN_TEXT | DOCF_NO_CURSOR;
            case LK_DOC:
                type = LK_FILE;
                break;

            case LK_DOC_ANCHOR:
                type = LK_FILE_ANCHOR;
                break;

            case LK_PLAIN_FIND:
                flags = DOCF_PLAIN_TEXT | DOCF_NO_CURSOR;
            case LK_DOC_FIND:
                type = LK_FILE_FIND;
                break;

            case LK_PLAIN_LINE:
                flags = DOCF_PLAIN_TEXT | DOCF_NO_CURSOR;
            case LK_DOC_LINE:
                type = LK_FILE_LINE;
                break;
            case LK_BIBLE_FIND:
                flags = DOCF_PLAIN_TEXT | DOCF_NO_CURSOR;
                break;
        }

        flags |= DOCF_ALLOW_UNDO;

        if (LK_DOC <= _type <= LK_DOC_LINE)
        {
            doc = Str2I64(filename);//See SpriteEdText()
            res = TRUE;
        }
        else
        {
            st1 = StrNew(filename);
            st2 = StrNew(filename);
            StrLastRemove(st1, "/", st2); //st2 is name without dir
            if (!FileNameCheck(st2))
                doc = NULL;
            else
            {
                Silent;
                if (Bt(&edf_dof_flags, EDf_BAIL)) //if bail, scan parents
                    res = FileFind(filename,, FUF_JUST_FILES | FUF_SCAN_PARENTS);
                else if (!(res = FileFind(filename,, FUF_JUST_FILES)))
                    other_found = FileFind(filename,, FUF_JUST_FILES | FUF_SCAN_PARENTS);
                doc = DocRead(filename, flags);
                doc->desc = 'Edit';
                Silent(old_silent);
                Fs->border_src = BDS_ED_FILENAME_DRIVE;
            }
            Free(st1);
            Free(st2);
        }
        if (!doc || doc->doc_signature != DOC_SIGNATURE_VAL)
            res = FALSE;
        else
        {
            if (Bt(&edf_dof_flags, EDf_COLLAPSE))
                DocCollapse(TRUE, doc);
            else if (Bt(&edf_dof_flags, EDf_UNCOLLAPSE))
                DocCollapse(FALSE, doc);

            if (res || other_found)
                switch (type)
                {
                    case LK_FILE_LINE:
                        res = DocGoToLine(doc, *_num);
                        break;

                    case LK_FILE_ANCHOR:
                        res = DocAnchorFind(doc, needle_str);
                        break;

                    case LK_FILE_FIND:
                        res = DocFind(doc,, needle_str, *_num);
                        break;
                    case LK_BIBLE_FIND:
                        res = DocFind(doc, *_num, needle_str);
                        break;

                    default:
                        DocCenter(doc);
                }
            *_num = doc->cur_entry->y + 1;

            if (edf_dof_flags & EDF_WAS_WRITE)
                res = FALSE;
            if (!(edf_dof_flags & EDF_BAIL))
            {
                if (*doc->filename.name)
                    doc->filename.dirc = DirContextNew(doc->filename.name);
                else
                    doc->filename.dirc = NULL;
                if (DocEd(doc, edf_dof_flags | DOF_DONT_HOME))
                {
                    DocLock(doc);
                    doc_e = doc->cur_entry;
                    if (doc_e != doc)
                        DocEntryRun(doc, doc_e, TRUE);
                    DocUnlock(doc);
                    if (!(LK_DOC <= _type <= LK_DOC_LINE))
                    {
                        DocWrite(doc);
                        if (edf_dof_flags & EDF_WAS_WRITE)
                            res = TRUE;
                    }
                }
                DirContextDel(doc->filename.dirc);
            }
            if (!(LK_DOC <= _type <= LK_DOC_LINE))
                DocDel(doc);
        }
    }
    catch
    {
        Silent(old_silent);
        res = FALSE;
    }
    Fs->border_src = old_border_src;

    return res;
}

#define DEFAULT_ADDR_LINK_BIN_SIZE  64

public I64 EdLinkConvert(U8 *link_st, U8 **_filename=NULL, U8 **_needle_str=NULL, I64 *_num=NULL, I64 edf_dof_flags=0)
{//Editor Link--> filename, needle_str and line number.
    U8          *st, *ptr, *src, *filename = NULL, *needle_str = NULL, *filename2;
    I64          res, i, num = 1;
    CHashSrcSym *tmph;

    if (!link_st || !*link_st)
    {
        if (edf_dof_flags & EDF_BAIL)
            return -1;
        link_st = blkdev.tmp_filename;
    }
    st = StrNew(link_st);
    res = LK_FILE;
    if (StrLen(st) > 3 && st[2] == ':')
    {
        st[2] = 0;
        filename2 = st + 3;
        switch (res = DefineMatch(st, "ST_LINK_TYPES", LMF_IGNORE_CASE))
        {
            case LK_MAN_PAGE:
                if (tmph = HashFind(filename2, Fs->hash_table, HTG_SRC_SYM))
                    res = EdLinkConvert(tmph->src_link, &filename, &needle_str, &num, edf_dof_flags);
                else
                    res = -1;
                goto lc_done;

            case LK_ADDR:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr = 0;
                    i = Str2I64(ptr + 1);
                }
                else
                    i = DEFAULT_ADDR_LINK_BIN_SIZE;
                if (ptr = SrcEdLink(ExePrint("%s;", filename2), i))
                {
                    res = EdLinkConvert(ptr, &filename, &needle_str, &num, edf_dof_flags);
                    Free(ptr);
                }
                else
                    res = -1;
                goto lc_done;

            case LK_DEF:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr = 0;
                    i = Str2I64(ptr + 1);
                }
                else
                    i = -1;
                filename = StrNew(filename2);
                num = i;
                goto lc_done;

            case LK_HELP_INDEX:
                filename = StrNew(filename2);
                goto lc_done;

            case LK_BIBLE_FIND:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr = 0;
                    src = ptr + 1;
                    while (*src)
                    {//We do not allow ending verse
                        if (*src == '-')
                            *src = 0;
                        src++;
                    }
                    needle_str = StrNew(ptr + 1);
                }
                i = DefineMatch(filename2, "ST_BIBLE_BOOKS", LMF_IGNORE_CASE);
                if (i < 0)
                    res = -1;
                else
                {
                    num = Str2I64(DefineSub(i, "ST_BIBLE_BOOK_LINES"));
                    filename2 = BIBLE_FILENAME;
                }
                break;

            case LK_FILE_LINE:
            case LK_PLAIN_LINE:
            case LK_DOC_LINE:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr = 0;
                    num = Str2I64(ptr + 1);
                }
                break;

            case LK_FILE_ANCHOR:
            case LK_DOC_ANCHOR:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr  =0;
                    needle_str = StrNew(ptr + 1);
                }
                break;

            case LK_FILE_FIND:
            case LK_PLAIN_FIND:
            case LK_DOC_FIND:
                if (ptr = StrLastOcc(filename2, ","))
                {
                    *ptr = 0;
                    needle_str = StrNew(ptr + 1);
                    if (ptr = StrLastOcc(needle_str, ":"))
                    {
                        *ptr = 0;
                        num = Str2I64(ptr + 1);
                    }
                }
                break;
        }
    }
    else
        filename2 = st;
    if (res >= 0)
    {
        if (LK_DOC <= res <= LK_DOC_LINE)
            filename = StrNew(filename2); //Holds document address as number.
        else
            filename = FileNameAbs(filename2);
    }
lc_done:
    Free(st);
    if (_filename)
        *_filename = filename;
    else
        Free(filename);
    if (_needle_str)
        *_needle_str = needle_str;
    else
        Free(needle_str);
    if (_num)
        *_num = num;

    return res;
}

public Bool DocLinkCheck(CDoc *doc, U8 *link_st)
{//Check for bad Editor Link.
    U8          *filename, *st;
    Bool         res = FALSE;
    CDirContext *dirc;

    if (link_st)
    {
        st = FileNameAbs(doc->filename.name);
        dirc = DirContextNew(st);
        Free(st);
        switch (EdLinkConvert(link_st, &filename))
        {
            case -1:
                break;

            case LK_FILE_LINE:
            case LK_PLAIN_LINE:
            case LK_FILE:
//We don't check line number
                res = FileFind(filename,, FUF_JUST_FILES | FUF_SCAN_PARENTS);
                break;

            case LK_BIBLE_FIND:
                st = StrNew(link_st + 3);
                if (StrOcc(st, ','))
                    StrLastRemove(st, ",");
                if (DefineMatch(st, "ST_BIBLE_BOOKS", LMF_IGNORE_CASE) >= 0)
                    res = TRUE;
                Free(st);
                break;

            default://TODO: Need to validate HI: and DN:
                if (Ed(link_st, EDF_BAIL))
                    res = TRUE;
        }
        Free(filename);
        DirContextDel(dirc);
    }

    return res;
}

public U8 *DocLinkFile(U8 *link_st, CTask *mem_task=NULL)
{//Return the file for an Editor Link Types.
    U8 *filename = NULL, *st, *res = NULL;

    if (link_st)
    {
        switch (EdLinkConvert(link_st, &filename))
        {
            case LK_FILE:
            case LK_FILE_ANCHOR:
            case LK_FILE_FIND:
            case LK_FILE_LINE:
            case LK_PLAIN:
            case LK_PLAIN_FIND:
            case LK_PLAIN_LINE:
                st = FileNameAbs(filename, FUF_SCAN_PARENTS);
                res = StrNew(st);
                Free(st);
                break;
            case LK_BIBLE_FIND:
                res = StrNew(BIBLE_FILENAME, mem_task);
                break;
        }
        Free(filename);
    }

    return res;
}