#help_index "DolDoc/File"

public U0 DocLoad(CDoc *doc, U8 *src2, I64 size)
{//Fetch doc from raw mem buf.
    I64      i;
    U8      *src;
    Bool     unlock = DocLock(doc);
    CDocBin *tmpb;

    doc->find_replace->filter_lines = 0;
    if (src2)
    {
        DocPutS(doc, src2); //Too big DocPrint() is wasteful.
        src = src2 + StrLen(src2) + 1;
        i = size - (offset(CDocBin.end) - offset(CDocBin.start));
        while (src <= src2 + i)
        {
            tmpb = CAlloc(sizeof(CDocBin), doc->mem_task);
            MemCopy(&tmpb->start, src, offset(CDocBin.end) - offset(CDocBin.start));
            src+=offset(CDocBin.end)-offset(CDocBin.start);
            tmpb->data = MAlloc(tmpb->size, doc->mem_task);
            if (tmpb->size)
            {
                MemCopy(tmpb->data, src, tmpb->size);
                src += tmpb->size;
            }
            QueueInsert(tmpb, doc->bin_head.last);
            if (tmpb->num >= doc->cur_bin_num)
                doc->cur_bin_num = tmpb->num + 1;
        }
    }
    if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)))
        DocBinsValidate(doc);
    DocTop(doc); //Calls DocRecalc().  DOCT_CURSOR will be set.
    if (unlock)
        DocUnlock(doc);
}

public CDoc *DocRead(U8 *name=NULL, I64 flags=0)
{//Fetch doc from disk. See flags.
    CDoc        *doc = DocNew;
    U8          *src, *name2;
    I64          size = 0;
    CDirContext *dirc;

    if (!name)
        name = blkdev.tmp_filename;
    doc->flags |= flags;
    name2 = FileNameAbs(name);
    StrCopy(doc->filename.name, name2);
    if (src = FileRead(name2, &size, &doc->file_attr))
    {
        if (dirc = DirContextNew(name2))
        {
            DocLoad(doc, src, size);
            DirContextDel(dirc);
        }
        Free(src);
    }
    Free(name2);

    return doc;
}

public U8 *DocSave(CDoc *doc, I64 *_size=NULL)
{//Store doc to raw mem buf.
    CDocEntry   *doc_e, *doc_e1;
    CDocBin     *b;
    Bool         unlock = DocLock(doc);
    I64          ch, count = 1;//terminator
    U8          *st, *res, *dst, *src;

    if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)))
        DocBinsValidate(doc);
    if (doc->flags & DOCF_NO_CURSOR)
        DocRecalc(doc);
    else
    {
        DocRecalc(doc, RECALCF_ADD_CURSOR);
        if (doc->head.next->type_u8 == DOCT_CURSOR)
            DocEntryDel(doc, doc->head.next); //If no cursor, DocLoad() puts at top.
    }
    for (doc_e = doc->head.next; doc_e != doc; doc_e = doc_e->next)
    {
        if (!Bt(doldoc.type_flags_data, doc_e->type_u8))
        {
            switch (doc_e->type_u8)
            {
                case DOCT_TAB:
                case DOCT_PAGE_BREAK:
                case DOCT_CURSOR:
                    count++;
                    break;

                case DOCT_NEW_LINE:
                    if (doc->flags & DOCF_CARRIAGE_RETURN)
                        count += 2;
                    else
                        count++;
                    break;

                case DOCT_SOFT_NEW_LINE:
                    break;

                case DOCT_TEXT:
                    if (!(doc_e->de_flags &
                        ~(DOCEF_TAG | DOCG_BL_IV_UL | DOCEF_WORD_WRAP | DOCEF_HIGHLIGHT|DOCEF_SKIP | DOCEF_FILTER_SKIP)) &&
                        !(doc_e->type & DOCG_BL_IV_UL))
                    {
                        count += StrLen(doc_e->tag);
                        if (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)) || doc->flags & DOCF_DBL_DOLLARS)
                            count += StrOcc(doc_e->tag, '$');
                        break;
                    }

                default:
                    st = Doc2PlainText(doc, doc_e);
                    count += StrLen(st) + 2;
                    Free(st);
            }
        }
    }
    for (b = doc->bin_head.next; b != &doc->bin_head; b = b->next)
        if (b->use_count > b->tmp_use_count)
            count += offset(CDocBin.end) - offset(CDocBin.start) + b->size;
    res = MAlloc(count);
    dst = res;
    doc_e = doc->head.next;
    while (doc_e != doc)
    {
        doc_e1 = doc_e->next;
        if (!Bt(doldoc.type_flags_data, doc_e->type_u8))
            switch (doc_e->type_u8)
            {
                case DOCT_CURSOR:
                    DocEntryDel(doc, doc_e);
                    *dst++ = CH_CURSOR;
                    break;

                case DOCT_TAB:
                    *dst++ = '\t';
                    break;

                case DOCT_NEW_LINE:
                    if (doc->flags & DOCF_CARRIAGE_RETURN)
                        *dst++ = '\r';
                    *dst++ = '\n';
                    break;

                case DOCT_SOFT_NEW_LINE:
                    break;

                case DOCT_TEXT:
                    if (!(doc_e->de_flags &
                        ~(DOCEF_TAG | DOCG_BL_IV_UL | DOCEF_WORD_WRAP | DOCEF_HIGHLIGHT | DOCEF_SKIP | DOCEF_FILTER_SKIP)) &&
                        !(doc_e->type & DOCG_BL_IV_UL))
                    {
                        src = doc_e->tag;
                        while (ch = *src++)
                        {
                            *dst++ = ch;
                            if (ch == '$' && (!(doc->flags & (DOCF_PLAIN_TEXT | DOCF_PLAIN_TEXT_TABS)) ||
                                                doc->flags & DOCF_DBL_DOLLARS))
                                *dst++ = ch;
                        }
                        break;
                    }

                default:
                    *dst++ = '$';
                    st = Doc2PlainText(doc, doc_e);
                    StrCopy(dst, st);
                    dst += StrLen(st);
                    *dst++ = '$';
                    Free(st);
            }
        doc_e = doc_e1;
    }
    *dst++ = 0;
    b = doc->bin_head.next;
    if (b != &doc->bin_head)
    {
        do
        {
            if (b->use_count > b->tmp_use_count)
            {
                MemCopy(dst, &b->start, offset(CDocBin.end) - offset(CDocBin.start));
                dst += offset(CDocBin.end) - offset(CDocBin.start);
                MemCopy(dst, b->data, b->size);
                dst += b->size;
            }
            b = b->next;
        }
        while (b != &doc->bin_head);

    }
    else
        count--; //No terminator
    if (_size)
        *_size = count;
    if (unlock)
        DocUnlock(doc);

    return res;
}

public Bool DocWrite(CDoc *doc, Bool prompt=FALSE)
{//Store doc to disk.
    I64 size;
    U8 *buf;

    if (prompt && !DocForm(&doc->filename) || doc->filename.name[0] == 'A' && doc->filename.name[2] == ':')
        return FALSE;  //CANCEL || LK_DOC,LK_DOC_ANCHOR,LK_DOC_FIND,LK_DOC_LINE?
    buf = DocSave(doc, &size);
    FileWrite(doc->filename.name, buf, size, 0, doc->file_attr);
    Free(buf);

    return TRUE;
}

#help_index "DolDoc"
public U0 DocInsDoc(CDoc *doc=NULL, CDoc *doc2)
{//Insert copy of doc2 into doc at insert pt, cur_entry.
//TODO: DocReset
    U8          *dst;
    Bool         unlock_doc, unlock_doc2 = DocLock(doc2);
    CDocEntry   *doc_ne, *doc_e = doc2->head.next, *doc_ce;

    if (!doc)
        doc = DocPut;
    unlock_doc = DocLock(doc), DocRemSoftNewLines(doc, NULL);
    doc_ce = doc->cur_entry;
    if (doc_ce->type_u8 == DOCT_TEXT && doc->cur_col > doc_ce->min_col)
    {
        if (doc->cur_col < doc_ce->max_col)
        {
            dst = doc_ce->tag + doc->cur_col;
            doc_ne = DocEntryNewTag(doc, doc_ce, dst);
            *dst = 0;
            doc_ne->type = DOCT_TEXT | doc_ce->type & 0xFFFFFF00;
            doc_ce->max_col = doc->cur_col;
            QueueInsert(doc_ne, doc_ce);
            doc->cur_entry = doc_ne;
            doc->cur_col = doc_ne->min_col;
        }
        else
            if (doc_ce != doc)
                doc->cur_entry = doc_ce->next;
    }
    while (doc_e != doc2)
    {
        if (doc_e->type_u8 != DOCT_SOFT_NEW_LINE)
        {
            doc_ne = DocEntryCopy(doc, doc_e);
            QueueInsert(doc_ne, doc->cur_entry->last);
        }
        doc_e = doc_e->next;
    }
    DocRecalc(doc);
    if (unlock_doc2)
        DocUnlock(doc2);
    if (unlock_doc)
        DocUnlock(doc);
}

#help_index "DolDoc/Compiler;Compiler/Directive"
public U0 StreamDoc(CDoc *doc)
{//Inject doc into compile stream. Use inside #exe{}.
//TODO: DocReset
    Bool         unlock_doc = DocLock(doc);
    CDocEntry   *doc_e = doc->head.next;

    while (doc_e != doc)
    {
        if (doc_e->type_u8 == DOCT_TEXT)
            StreamPrint("%s", doc_e->tag);
        else if (doc_e->type_u8 == DOCT_NEW_LINE)
            StreamPrint("\n");
        else if (doc_e->type_u8 == DOCT_TAB)
            StreamPrint("\t");
        doc_e = doc_e->next;
    }
    if (unlock_doc)
        DocUnlock(doc);
}

#help_index "DolDoc"
Bool DocCaptureUndo(CDoc *doc, Bool force=FALSE)
{
    Bool         res = FALSE, unlock;
    I64          time_stamp, flags;
    CDocUndo    *u;

    if (doc->flags & DOCF_ALLOW_UNDO)
    {
        unlock = DocLock(doc);
        time_stamp = TSCGet;
        if (doc->flags & DOCF_UNDO_DIRTY &&
            time_stamp > doc->undo_head.last->time_stamp + counts.time_stamp_freq << 4 ||
            force)
        {
            u = CAlloc(sizeof(CDocUndo), doc->mem_task);
            u->time_stamp = time_stamp;
            flags = doc->flags;
            doc->flags &= ~DOCF_NO_CURSOR;
            u->body = DocSave(doc, &u->size);
            doc->flags = flags;
            QueueInsert(u, doc->undo_head.last);
            doc->flags &= ~DOCF_UNDO_DIRTY;
            doc->undo_count++;
            u->doc_flags = doc->flags;
            res = TRUE;
            if (doc->flags & DOCF_AUTO_SAVE)
                DocWrite(doc);
        }
        if (unlock)
            DocUnlock(doc);
    }
    return res;
}
 
U0 DocUndoRestore(CDoc *doc)
{
    Bool         unlock = DocLock(doc);
    CDocUndo    *u = doc->undo_head.last, *u_next, *u_last;

    if (u != &doc->undo_head)
    {
        QueueRemove(u);
        u_next = doc->undo_head.next;
        u_last = doc->undo_head.last;
        QueueInit(&doc->undo_head);
        DocReset(doc, TRUE);
        doc->flags = u->doc_flags & ~DOCF_NO_CURSOR;
        DocLoad(doc, u->body, u->size);
        doc->flags = u->doc_flags;
        DocUndoDel(doc, u);
        doc->undo_head.next = u_next;
        doc->undo_head.last = u_last;
    }
    DocUndoCountSet(doc);
    doc->flags &= ~DOCF_UNDO_DIRTY;
    if (unlock)
        DocUnlock(doc);
}

#help_index "Graphics/GR Files;DolDoc/Output;StdOut/DolDoc"
public Bool DocType(CDoc *doc=NULL, U8 *filename, I64 trailing_new_lines=1)
{//Output txt or graphic file to document.
    Bool  res = FALSE;
    CDoc *doc2;

    if (!doc && !(doc = DocPut) || doc->doc_signature != DOC_SIGNATURE_VAL)
        return FALSE;
    if (FilesFindMatch(filename, FILEMASK_TXT))
    {
        doc2 = DocRead(filename);
        DocInsDoc(doc, doc2);
        if (IsRaw)
            DocDumpLines(doc2, 100000);
        DocDel(doc2);
        res = TRUE;
    }
    else if (FilesFindMatch(filename, "*.GR*"))
    {
        DocGR(doc, filename);
        res = TRUE;
    }
    if (res)
        DocPrint(doc, "%h*c", trailing_new_lines, '\n');

    return res;
}

#help_index "Graphics/GR Files;"\
            "File/Cmd Line (Typically);DolDoc/Cmd Line (Typically);"\
            "StdOut;Cmd Line (Typically)"
public Bool Type(U8 *filename, I64 trailing_new_lines=1)
{//Output txt or graphic file to command line.
    return DocType(, filename, trailing_new_lines);
}

#help_index "DolDoc/File"

public U8 *DocLineRead(U8 *filename, I64 line, CTask *mem_task=NULL)
{//Extract line from stored doc file. (Slow.)
    U8   *res = NULL;
    CDoc *doc = DocRead(filename, DOCF_PLAIN_TEXT_TABS | DOCF_NO_CURSOR);

    if (DocGoToLine(doc, line) && doc->cur_entry->type_u8 == DOCT_TEXT)
        res = StrNew(doc->cur_entry->tag, mem_task);
    DocDel(doc);

    return res;
}

public U8 *DocLineWrite(U8 *filename, I64 line, U8 *st)
{//Write line to stored doc file. (Slow.)
    U8   *res = NULL;
    CDoc *doc = DocRead(filename, DOCF_PLAIN_TEXT_TABS | DOCF_NO_CURSOR);

    if (DocGoToLine(doc, line))
    {
        if (doc->cur_entry->type_u8 == DOCT_TEXT)
        {
            Free(doc->cur_entry->tag);
            doc->cur_entry->tag = StrNew(st);
        }
        else
            DocPrint(doc, "%s", st);
        DocTop(doc);
        DocWrite(doc);
    }
    DocDel(doc);

    return res;
}