
/*
 * IO.C
 * (c) 1992 J.Harper
 *
 * functions for loading files,	reading	clips, etc
 */

#include "jed.h"
#include "jed_protos.h"

Prototype   BOOL    readtx	    (TX	*, STRPTR, LONG);
Prototype   BOOL    writetx	    (TX	*, VW *, STRPTR);
Prototype   VALUE * cmd_openfile    (LONG, VALUE *);
Prototype   VALUE * cmd_savefileas  (LONG, VALUE *);
Prototype   VALUE * cmd_savefile    (LONG, VALUE *);
Prototype   VALUE * cmd_savesection (LONG, VALUE *);
Prototype   VOID    doserror	    (VOID);
Prototype   VALUE * cmd_cd	    (LONG, VALUE *);
Prototype   VOID    killclipstuff   (VOID);
Prototype   BOOL    writeclip	    (LONG, POS *, POS *);
Prototype   STRPTR  readclip	    (LONG);
Prototype   BOOL    writeline	    (STRPTR, WORD, BPTR, VW *);
Local	    BOOL    dobackup	    (STRPTR, VW	*);

/* bytes to copy instead of tabs */
Prototype   const UBYTE	Spaces[];
Prototype   const UBYTE	Tabs[];
const UBYTE Spaces[] = "                                                "
		       "                                                "
		       "                                                ";
const UBYTE Tabs[] =   "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t";

/*
 * read	a file into a tx structure, the	line list should have been
 * killed.
 * I'm going to have to speed this up somehow.
 * hopefully now loads lines of	any length (upto max of	32768)
 */
#define	SIZESTEP  50	/* size	at which line list grows by */
#define	BUFFSTART 1024
BOOL
readtx(TX *tx, STRPTR fileName,	LONG diskTab)
{
    BOOL rc = FALSE;
    BPTR fh;
    if(fileName	&& (*fileName) && (fh =	Open(fileName, MODE_OLDFILE)))
    {
	STRPTR buff;
	ULONG bufflen =	BUFFSTART;
	tx->tx_FileName	= savestring(fileName);
	tx->tx_TitleName = savestring(FilePart(fileName));

	if(buff	= AllocVec(bufflen, 0L))
	{
	    LONG c, i =	0;
	    LONG linenum = 0, allocedlines = 0;

	    settitle("loading...");
	    while((c = FGetC(fh)) != -1)
	    {
		if(i >=	bufflen)
		{
		    if(i >= 32768)
		    {
			settitle("error: line too long (>32768)");
			goto abort;
		    }
		    STRPTR newbuff = AllocVec(bufflen << 1, 0L);
		    if(!newbuff)
			goto abortmem;
		    memcpy(newbuff, buff, i);
		    FreeVec(buff);
		    buff = newbuff;
		    bufflen <<=	1;
		}
		if(c ==	'\n')
		{
		    STRPTR newstr;
		    LINE *line;
		    if(linenum >= allocedlines)
		    {
			if(!resizelinelist(tx, SIZESTEP, linenum))
			    goto abortmem;
			allocedlines +=	SIZESTEP;
		    }
		    if(!(newstr	= AllocVec(i + 1, 0L)))
			goto abortmem;
		    memcpy(newstr, buff, i);
		    newstr[i] =	0;
		    line = tx->tx_Lines	+ linenum;
		    line->ln_Strlen = i	+ 1;
		    line->ln_Line = newstr;
		    i =	0;
		    linenum++;
		}
		else if(c == '\t')
		{
		    WORD numspaces = diskTab - (i % diskTab);
		    if(i + numspaces >=	bufflen)
		    {
			STRPTR newbuff = AllocVec(bufflen << 1,	0L);
			if(!newbuff)
			    goto abortmem;
			memcpy(newbuff,	buff, i);
			FreeVec(buff);
			buff = newbuff;
			bufflen	<<= 1;
		    }
		    strncpy(buff + i, Spaces, numspaces);
		    i += numspaces;
		}
		else
		    buff[i++] =	c;
	    }
	    if(i)
	    {
		STRPTR newstr;
		LINE *line;
		if(!resizelinelist(tx, linenum - allocedlines +	1, linenum))
		    goto abortmem;
		if(!(newstr = AllocVec(i + 1, 0L)))
		    goto abortmem;
		memcpy(newstr, buff, i);
		newstr[i] = 0;
		line = tx->tx_Lines + linenum;
		line->ln_Strlen	= i + 1;
		line->ln_Line =	newstr;
		linenum++;
	    }
	    else
		if(!resizelinelist(tx, linenum - allocedlines, linenum))
		    goto abortmem;
	    tx->tx_Changes = 0;
	    rc = TRUE;
	    settitle("OK");
	    /* This block only gets executed if	we aborted while
	     * reading the file.
	     */
	    if(0)
	    {
abortmem:
		settitle(NoMemMsg);
abort:
		clearlinelist(tx);
	    }
	    FreeVec(buff);
	}
	else
	    settitle(NoMemMsg);
	Close(fh);
    }
    else
    {
	if(fileName && (*fileName))
	{
	    tx->tx_FileName = savestring(fileName);
	    tx->tx_TitleName = savestring(FilePart(fileName));
	}
	else
	{
	    tx->tx_FileName = savestring("");
	    tx->tx_TitleName = savestring("Untitled");
	}
	tx->tx_Changes = 0;
	if(clearlinelist(tx))
	    rc = TRUE;
	else
	    settitle(NoMemMsg);
    }
    return(rc);
}

BOOL
writetx(TX *tx,	VW *vw,	STRPTR fileName)
{
    BOOL rc = TRUE;
    if(fileName)
    {
	BPTR fh	= Open(fileName, MODE_NEWFILE);
	if(fh)
	{
	    LONG i;
	    LINE *line = tx->tx_Lines;
	    settitle("saving...");
	    for(i = 0; i < tx->tx_NumLines; i++, line++)
	    {
		if(!writeline(line->ln_Line, line->ln_Strlen - 1, fh, vw))
		{
		    doserror();
		    rc = FALSE;
		    break;
		}
		FPutC(fh, '\n');
	    }
	    Close(fh);
	}
	else
	{
	    doserror();
	    rc = FALSE;
	}
	if(rc)
	    settitle("OK");
    }
    else
    {
	settitle(NoMemMsg);
	rc = FALSE;
    }
    return(rc);
}

/*
 * (openfile `fileName')
 */
VALUE *
cmd_openfile(LONG argc,	VALUE *argv)
{
    if(TPLATE1(VTF_STRING))
    {
	VW *vw = CurrVW;
	TX *tx = vw->vw_Tx;
	LONG rc;

	if((!tx->tx_Changes) ||	(ezreq("OK to lose %ld changes\nto file %s", "Yeah|Cancel", tx->tx_Changes, tx->tx_TitleName)))
	{
	    cursor(OFF);
	    killlinelist(tx);
	    freestring(tx->tx_FileName);
	    freestring(tx->tx_TitleName);
	    if(readtx(tx, ARG1.val_Value.String, vw->vw_Prefs.prf_DiskTab))
		rc = TRUE;
	    else
	    {
		clearlinelist(tx);  /* hope for	some mem left */
		rc = FALSE;
	    }
	    setupfileprefs(tx);
	    updatedimensions(vw);
	    resetallviews(tx);
	}
	else
	    rc = FALSE;
	resetslptxtitles(tx);
	setnumres(rc);
    }
    return(&RES);
}

/*
 * (savefileas `fileName')
 */
VALUE *
cmd_savefileas(LONG argc, VALUE	*argv)
{
    if(TPLATE1(VTF_STRING))
    {
	TX *tx = CurrVW->vw_Tx;

	dobackup(ARG1.val_Value.String,	CurrVW);
	if(writetx(tx, CurrVW, ARG1.val_Value.String))
	{
	    freestring(tx->tx_FileName);
	    freestring(tx->tx_TitleName);
	    tx->tx_FileName = savestring(ARG1.val_Value.String);
	    tx->tx_TitleName = savestring(FilePart(ARG1.val_Value.String));
	    tx->tx_Changes = 0;
	    savefileprefs(tx);
	    setnumres(TRUE);
	}
	else
	    setnumres(FALSE);
	resetslptxtitles(tx);
    }
    return(&RES);
}

/*
 * (savefile)
 */
VALUE *
cmd_savefile(LONG argc,	VALUE *argv)
{
    TX *tx = CurrVW->vw_Tx;

    if(tx->tx_FileName && tx->tx_FileName[0])
    {
	dobackup(tx->tx_FileName, CurrVW);
	if(writetx(tx, CurrVW, tx->tx_FileName))
	{
	    tx->tx_Changes = 0;
	    savefileprefs(tx);
	    returnnum(TRUE);
	}
    }
    else
	settitle("error: file is untitled");
    returnnum(FALSE);
}

/*
 * (savesection	`sectionType' `fileName')
 */
VALUE *
cmd_savesection(LONG argc, VALUE *argv)
{
    if(TPLATE2(VTF_STRING, VTF_STRING))
    {
	VW *vw = CurrVW;
	POS endpos;
	POS oldcurs = vw->vw_CursorPos;
	BOOL rc	= TRUE;

	padcursor();
	if(getsection(ARG1.val_Value.String, &endpos))
	{
	    BPTR fh = Open(ARG2.val_Value.String, MODE_NEWFILE);
	    if(fh)
	    {
		LINE *line = vw->vw_Tx->tx_Lines + vw->vw_CursorPos.pos_Line;
		if(endpos.pos_Line == vw->vw_CursorPos.pos_Line)
		{
		    if(!writeline(line->ln_Line	+ vw->vw_CursorPos.pos_Col, endpos.pos_Col - vw->vw_CursorPos.pos_Col, fh, vw))
			doserror();
		}
		else
		{
		    if(vw->vw_CursorPos.pos_Col)
		    {
			if(!writeline(line->ln_Line + vw->vw_CursorPos.pos_Col,	line->ln_Strlen	- vw->vw_CursorPos.pos_Col - 1,	fh, vw))
			    goto writeerr;
			FPutC(fh, '\n');
			vw->vw_CursorPos.pos_Line++;
			line++;
		    }
		    while(vw->vw_CursorPos.pos_Line < endpos.pos_Line)
		    {
			if(!writeline(line->ln_Line, line->ln_Strlen - 1, fh, vw))
			    goto writeerr;
			FPutC(fh, '\n');
			vw->vw_CursorPos.pos_Line++;
			line++;
		    }
		    if(endpos.pos_Col)
		    {
			if(!writeline(line->ln_Line, endpos.pos_Col, fh, vw))
			{
writeerr:
			    doserror();
			    rc = FALSE;
			}
		    }
		}
		Close(fh);
	    }
	    else
	    {
		doserror();
		rc = FALSE;
	    }
	}
	else
	    rc = FALSE;
	vw->vw_CursorPos = oldcurs;
	setnumres(rc);
    }
    return(&RES);
}

/*
 * Sets	title bar to message describing	previous DOS error.
 */
VOID
doserror(VOID)
{
    UBYTE errmsg[81];
    if(Fault(IoErr(), "DOS error", errmsg, 81))
	settitle(errmsg);
    else
	settitlefmt("DOS error: %ld", IoErr());
}

/*
 * (cd `dir')
 */
VALUE *
cmd_cd(LONG argc, VALUE	*argv)
{
    BOOL rc = FALSE;
    if(TPLATE1(VTF_STRING))
    {
	BPTR lock = Lock(ARG1.val_Value.String,	SHARED_LOCK);
	if(lock)
	{
	    lock = CurrentDir(lock);
	    UnLock(lock);
	    rc = TRUE;
	}
	else
	    settitlefmt("can't lock %s", (LONG)ARG1.val_Value.String);
    }
    returnnum(rc);
}

/*
 * IFF code, adapted from the newiff/other/clipftxt.c example on the 2.0
 * native developer set.
 */

#define	ID_FTXT	 MAKE_ID('F','T','X','T')
#define	ID_CHRS	 MAKE_ID('C','H','R','S')

Local const STRPTR IFFErrorMsgs[] =
{
    "End of file.",
    "End of context.",
    "No lexical scope.",
    "No memory.",
    "Stream read error.",
    "Stream write error.",
    "Stream seek error.",
    "File is corrupt.",
    "IFF syntax error.",
    "Not an IFF file.",
    "Required call-back hook missing.",
    "Return to client."
};

	APTR   IFFParseBase;
Local	STRPTR InternalClip;
Local	LONG   IClipLen;	/* not including zero terminator */

VOID
killclipstuff(VOID)
{
    freestring(InternalClip);
    InternalClip = NULL;
    IClipLen = 0;
    if(IFFParseBase)
    {
	CloseLibrary(IFFParseBase);
	IFFParseBase = NULL;
    }
}

/*
 * Writes a section of text to the clipboard, *startPos	IS trashed (it will
 * point to the	end of the copied section (maybe)).
 * if unit is less than	0 the text is copied to	my internal clipboard.
 */
BOOL
writeclip(LONG unit, POS *startPos, POS	*endPos)
{
    if(unit < 0)
    {
	IClipLen = sectionlength(startPos, endPos);
	freestring(InternalClip);
	if(InternalClip	= AllocVec(IClipLen + 1, 0L))
	{
	    copysection(startPos, endPos, InternalClip);
	    InternalClip[IClipLen] = 0;
	    return(TRUE);
	}
	settitle(NoMemMsg);
	return(FALSE);
    }
    else if(IFFParseBase || (IFFParseBase = OpenLibrary("iffparse.library", 36)))
    {
	struct IFFHandle *iff;
	LONG error = 0;
	if(iff = AllocIFF())
	{
	    if(iff->iff_Stream = (ULONG)OpenClipboard(unit))
	    {
		InitIFFasClip(iff);
		if(!(error = OpenIFF(iff, IFFF_WRITE)))
		{
		    if(!(error = PushChunk(iff,	ID_FTXT, ID_FORM, IFFSIZE_UNKNOWN)))
		    {
			if(!(error = PushChunk(iff, 0, ID_CHRS,	IFFSIZE_UNKNOWN)))
			{
			    LINE *line = CurrVW->vw_Tx->tx_Lines + startPos->pos_Line;
			    LONG writelen;
			    if(endPos->pos_Line	== startPos->pos_Line)
			    {
				writelen = endPos->pos_Col - startPos->pos_Col;
				if(WriteChunkBytes(iff,	line->ln_Line +	startPos->pos_Col, writelen) !=	writelen)
				    error = IFFERR_WRITE;
			    }
			    else
			    {
				if(startPos->pos_Col)
				{
				    writelen = line->ln_Strlen - startPos->pos_Col - 1;
				    if(WriteChunkBytes(iff, line->ln_Line + startPos->pos_Col, writelen) != writelen)
					goto writeerr;
				    if(WriteChunkBytes(iff, "\n", 1) != 1)
					goto writeerr;
				    startPos->pos_Line++;
				    line++;
				}
				while(startPos->pos_Line < endPos->pos_Line)
				{
				    writelen = line->ln_Strlen - 1;
				    if(WriteChunkBytes(iff, line->ln_Line, writelen) !=	writelen)
					goto writeerr;
				    if(WriteChunkBytes(iff, "\n", 1) != 1)
					goto writeerr;
				    startPos->pos_Line++;
				    line++;
				}
				if(endPos->pos_Col)
				{
				    writelen = endPos->pos_Col;
				    if(WriteChunkBytes(iff, line->ln_Line, writelen) !=	writelen)
				    {
writeerr:
					error =	IFFERR_WRITE;
				    }
				}
			    }
			    if(!error)
				error =	PopChunk(iff);
			    else
				PopChunk(iff);
			}
			if(!error)
			    error = PopChunk(iff);
			else
			    PopChunk(iff);
		    }
		    CloseIFF(iff);
		}
		CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
	    }
	    else
		error =	IFFERR_NOMEM;	/* ?? */
	    FreeIFF(iff);
	}
	else
	    error = IFFERR_NOMEM;

	CloseLibrary(IFFParseBase);
	IFFParseBase = NULL;

	if(error)
	{
	    settitlefmt("clip error: %s", (LONG)IFFErrorMsgs[-error - 1]);
	    return(FALSE);
	}
	return(TRUE);
    }
    settitle("need iffparse.library");
    return(FALSE);
}

/*
 * result should be FreeVec()'ed after use if non-NULL. A unit of -1
 * reads the internal clip.
 *
 * note:
 *  Currently this only	reads the first	CHRS chunk that	it finds.
 */
STRPTR
readclip(LONG unit)
{
    STRPTR text	= NULL;
    if(unit < 0)
    {
	if(IClipLen)
	{
	    if(text = AllocVec(IClipLen	+ 1, 0L))
	    {
		memcpy(text, InternalClip, IClipLen);
		text[IClipLen] = 0;
	    }
	    else
		settitle(NoMemMsg);
	}
	else
	    settitle("error: nothing on internal clipboard");
    }
    else if(IFFParseBase || (IFFParseBase = OpenLibrary("iffparse.library", 36)))
    {
	struct IFFHandle *iff;
	LONG error;
	if(iff = AllocIFF())
	{
	    if(iff->iff_Stream = (ULONG)OpenClipboard(unit))
	    {
		InitIFFasClip(iff);
		if(!(error = OpenIFF(iff, IFFF_READ)))
		{
		    if(!(error = StopChunk(iff,	ID_FTXT, ID_CHRS)))
		    {
			struct ContextNode *cn;
			if(!(error = ParseIFF(iff, IFFPARSE_SCAN)))
			{
			    cn = CurrentChunk(iff);
			    if(cn && (cn->cn_Type == ID_FTXT) && (cn->cn_ID == ID_CHRS))
			    {
				if(text	= AllocVec(cn->cn_Size + 1, 0L))
				{
				    error = ReadChunkBytes(iff,	text, cn->cn_Size);
				    if(error > 0)
				    {
					text[error] = 0;
					error =	0;
				    }
				}
				else
				    error = IFFERR_NOMEM;
			    }
			    else
				error =	IFFERR_NOTIFF;	/* ?? */
			}
		    }
		    CloseIFF(iff);
		}
		CloseClipboard((struct ClipboardHandle *)iff->iff_Stream);
	    }
	    else
		error =	IFFERR_NOMEM;
	    FreeIFF(iff);
	}
	else
	    error = IFFERR_NOMEM;
	if(error)
	{
	    if(text)
	    {
		FreeVec(text);
		text = NULL;
	    }
	    settitlefmt("clip error: %s", (LONG)IFFErrorMsgs[-error - 1]);
	}
	CloseLibrary(IFFParseBase);
	IFFParseBase = NULL;
    }
    else
	settitle("need iffparse.library");
    return(text);
}

/*
 * Writes out a	line of	text,
 * if SaveTabs == 1 leading spaces will	be changed to tabs,
 * if SaveTabs == 2 all	spaces (except after quotes) are made into tabs	and
 * trailing spaces are discarded.
 */
BOOL
writeline(STRPTR line, WORD len, BPTR fh, VW *vw)
{
    if(vw->vw_Prefs.prf_SaveTabs)
    {
	WORD lastchg = 0;
	BOOL inspc = FALSE;
	BOOL nodo = FALSE;
	LONG disktab = vw->vw_Prefs.prf_DiskTab;
	WORD i;
	UBYTE c;
	for(i =	0; (i <= len) && (c = line[i]);	i++)
	{
	    if(!nodo)
	    {
		if(c ==	' ')
		{
		    if(!inspc)
		    {
			if(FWrite(fh, line + lastchg, 1, i - lastchg) != i - lastchg)
			    goto error;
			lastchg	= i;
			inspc =	TRUE;
		    }
		}
		else
		{
		    if(inspc)
		    {
			WORD numtabs, numspcs;
			if(lastchg == i	- 1)
			{
			    numtabs = 0;
			    numspcs = 1;
			}
			else
			{
			    numtabs = (i - (lastchg - (lastchg % disktab))) / disktab;
			    numspcs = i	% disktab;
			    if(numspcs > i - lastchg)
				numspcs	= i - lastchg;
			}
			if(FWrite(fh, Tabs, 1, numtabs)	!= numtabs)
			    goto error;
			if(FWrite(fh, Spaces, 1, numspcs) != numspcs)
			    goto error;
			lastchg	= i;
			inspc =	FALSE;
		    }
		    if((c == '"') || (c == '\'') || (c == '`') || (vw->vw_Prefs.prf_SaveTabs == 1))
			nodo = TRUE;
		}
	    }
	}
	if((!inspc) || nodo)
	{
	    if(FWrite(fh, line + lastchg, 1, i - lastchg) != i - lastchg)
		goto error;
	}
	return(TRUE);
    }
    else
    {
	if(FWrite(fh, line, 1, len) == len)
	    return(TRUE);
    }
error:
    return(FALSE);
}

/*
 * Sorts out backup files.
 * Backups are saved to	the directory set by (setpref `bakdir'). The
 * number of backups kept is determined	by (setpref `baknum').
 * for a file called `foo' it's backups would be something like this,
 *  t:foo.bak1
 *  t:foo.bak2
 *  t:foo.bakN
 */
#define	COPYSTEP 65536
Local BOOL
dobackup(STRPTR	fileName, VW *vw)
{
    BOOL rc = TRUE;
    if(vw->vw_Prefs.prf_MaxBak > 0)
    {
	UBYTE namebuff[110];
	WORD i;
	BPTR srcfh = 0,	dstfh =	0;
	strcpy(namebuff, vw->vw_Prefs.prf_BakDir);
	if(AddPart(namebuff, FilePart(fileName), 100))
	{
	    for(i = vw->vw_Prefs.prf_MaxBak - 1; i; i--)
	    {
		UBYTE srcbuff[110];
		UBYTE dstbuff[110];
		sprintf(srcbuff, "%s.bak%ld", namebuff, i);
		sprintf(dstbuff, "%s.bak%ld", namebuff, i + 1);
		DeleteFile(dstbuff);
		Rename(srcbuff,	dstbuff);
	    }
	}
	else
	{
	    settitle("error: backup directory name too long");
	    return(FALSE);
	}
	strcat(namebuff, ".bak1");
	if((srcfh = Open(fileName, MODE_OLDFILE)) && (dstfh = Open(namebuff, MODE_NEWFILE)))
	{
	    STRPTR copybuff;
	    if(copybuff	= AllocVec(COPYSTEP, 0L))
	    {
		LONG actual;
		do
		{
		    if((actual = Read(srcfh, copybuff, COPYSTEP)) < 0)
		    {
			doserror();
			rc = FALSE;
			break;
		    }
		    if(Write(dstfh, copybuff, actual) != actual)
		    {
			doserror();
			rc = FALSE;
			break;
		    }
		}
		while(actual ==	COPYSTEP);
		FreeVec(copybuff);
	    }
	    else
	    {
		settitle(NoMemMsg);
		rc = FALSE;
	    }
	}
	if(dstfh)
	    Close(dstfh);
	if(srcfh)
	    Close(srcfh);
    }
    return(rc);
}

