#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 $LK,"DocPrint",A="MN: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 $LK,"DocRecalc",A="MN:DocRecalc"$().  DOCT_CURSOR will be set.
	if (unlock)
		DocUnlock(doc);
}

public CDoc *DocRead(U8 *name=NULL, I64 flags=0)
{//Fetch doc from disk. See $LK,"flags",A="MN:DOCF_PLAIN_TEXT"$.
	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, $LK,"DocLoad",A="MN: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: $LK,"DocReset",A="MN: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: $LK,"DocReset",A="MN: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)
			DocDump(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;
}
