/*
 * LEVEE, or Captain Video;  A vi clone
 *
 * Copyright (c) 1982-2008 David L Parsons
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, without or
 * without modification, are permitted provided that the above
 * copyright notice and this paragraph are duplicated in all such
 * forms and that any documentation, advertising materials, and
 * other materials related to such distribution and use acknowledge
 * that the software was developed by David L Parsons (orc@pell.chi.il.us).
 * My name may not be used to endorse or promote products derived
 * from this software without specific prior written permission.
 * THIS SOFTWARE IS PROVIDED AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE.
 */
#include "levee.h"
#include "extern.h"
#include <stdlib.h>
    
/* do some undoable modification */

/* These variables make docommand nonrecursive */

bool	ok;
int	newend,		/* end position after change */
	disp,		/* start redisplay here */
	newc,		/* new cursor position for wierd cmds */
	endY;		/* final yp for endp */

/* move a line of text right || left */

VOID PROC
adjuster(sleft, endd, sw)
bool sleft;
int endd, sw;
{
    bool noerror;
    int np, ts,
	ip,
	ss, adjp,
	DLEnum;

    if (sw == -1)
	sw = shiftwidth;
    if (sleft)
	sw = -sw;
    curr = bseekeol(curr);
    ip = curr;
    noerror = TRUE;
    do {
	DLEnum = sw + findDLE(ip, &np, bufmax,0);
	if (DLEnum >= 0 && DLEnum <= COLS && core[np] != EOL && np < bufmax) {
	    ts = DLEnum / tabsize;
	    ss = DLEnum % tabsize;
	    adjp = ts+ss+ip;
	    if (np-adjp < 0) {	/* expand the buf */
		moveright(&core[np], &core[adjp], bufmax-np);
		insert_to_undo(&undo, adjp, adjp - np);
	    }
	    else
		delete_to_undo(&undo, adjp, np - adjp);

	    endd += (adjp-np);
	    noerror = move_to_undo(&undo, ip, ts+ss);
	    fillchar(&core[ip], ts, TAB);
	    fillchar(&core[ip+ts], ss, 32);
	}
	else if (np > ip) {	/* remove the indent code */
	    noerror = delete_to_undo(&undo, ip, np-ip);
	    endd += (ip - np);
	}
	ip = 1 + fseekeol(ip);
    } while (noerror && ip < endd);
    if (!noerror)
	error();
    newc = skipws(curr);
    disp = curr;
    newend = endd;
    endY = setY(min(newend, pend));
}

/* join <count> lines together */

VOID PROC
join(count)
int count;
{
    bool ok;
    int lp, first;
    
    if (lend < bufmax) {	/* are we in the buffer? */
	disp = lend;				/* start redraw here */
	newc = lend;
	do {					/* join until.. */
	    first = lend;
	    lp = skipws(1+first);
	    ok = delete_to_undo(&undo, 1+first, lp-(1+first));
	    if (ok) {
		ok = move_to_undo(&undo, first, 1);
		core[first] = ' ';		/* spaces between lines */
	    }
	    count--;
	    lend = fseekeol(first);
	} while (ok && count > 0);
	endY = MAGICNUMBER;
	newend = lend;
	if (!ok)
	    error();
    }
    else
	error();
}

VOID PROC
squiggle(endp, c, dorepl)
int endp;
char c;
bool dorepl;
{
    int i;
    
    if (endp >= curr) {
	ok = move_to_undo(&undo,curr,endp-curr+1);
	if (ok) {
	    for (i = curr;i<=endp;i++) {
		if (!dorepl) {		/* squiggle it to uc - lc */
		    if (core[i] >='A' && core[i] <='Z')
			core[i] += 32;
		    else if (core[i]>='a' && core[i]<='z')
			core[i] -= 32;
		}
		else
		    core[i] = c;
	    }
	    newend = min(endp+1,lend);
	}
    }
}

VOID PROC
bigreplace()
{
    int len, tsiz;
    
    tsiz = lend-curr;
    if (move_to_undo(&undo, curr, tsiz))
	if (SIZE - bufmax > tsiz) {	/* enough room for temp copy? */
	    moveleft(&core[curr], &core[bufmax],lend-curr);
	    if (line(core, curr, lend, &len) != ESC)
		error();
	    newend = curr+len;
	    moveright(&core[bufmax+len], &core[newend], lend-newend);
	}
}

bool PROC
put(before)
bool before;
{
    endY = setY(curr);
    if (!before)
	if (yank.lines)
	    curr = min(lend+1,bufmax);
	else
	    curr = min(curr+1, lend);
    else if (yank.lines)
	curr = lstart;
    newc = disp = curr;
    return(putback(curr, &newend));
}

bool PROC
execute(start, end)
{
    int tf;
    FILE *f;
    char scratch[20];
    bool ret = FALSE;
    int size;

    strcpy(scratch, "/tmp/lv.XXXXXX");
    
    clrprompt();
    printch('!');
    if ( !getline(instring) )
	return FALSE;

    if ( (tf = mkstemp(scratch)) < 0 ) {
	prints("[tempfile error]");
	return FALSE;
    }

    strcat(instring, " 2>&1 <");
    strcat(instring, scratch);
    
    if ( (size = write(tf, core+start, end-start)) == (end-start) ) {
	if ( (f=popen(instring, "r")) ) {
	    if ( deletion(start, end) && (insertfile(f, 1, start, &size) > 0) )
		ret = TRUE;
	    pclose(f);
	}
	else
	    error();
    }
	
    close(tf);
    unlink(scratch);
    
    return ret;
}

VOID PROC
gcount()
{
    do {
	count = (count*10) + (ch-'0');
	readchar(); /* get a char to replace the one we dumped */
    } while ( count >= 0 && isdigit(ch) );
}

VOID PROC
docommand(cmd)
cmdtype cmd;
{
    cmdtype movecmd;	/* movement command for y, d, c */
    char    cmdch;
    int     oldc;	/* old count */
    int     endp;	/* end position before change */
    extern bool s_wrapped;

    resetX();				/* un-derange the cursor */
    oldc = newc = -1;
    endY = yp;
    newend = disp = curr;
    ok = TRUE;				/* so far everything is working */
    cmdch = ch;
    if (cmd != UNDO_C && cmd != YANK_C) {
	if (macro<0)
	    zerostack(&undo);
	if (redoing != TRUE) {
	    rcp = rcb;		/* point at start of redo buffer */
	    if (count > 1) {	/* put in a count? */
		numtoa(rcb,count);
		rcp += strlen(rcb);
	    }
	    *rcp++ = cmdch;	/* the command char goes in... */
	    xerox = TRUE;	/* hoist the magical flags */
	}
    }

    if (cmd <= YANK_C) {
	readchar();
	if ( isdigit(ch) && ch != '0' ) {
	    oldc = count;
	    count = 0;
	    gcount();				/* get a new count */
	    if (cmd == ADJUST_C)		/* special for >>,<< wierdness */
		swap(&count, &oldc);		/* reverse sw & count */
	    else
		count = count*max(oldc,1);	/* combine them */
	}
	if (ch == cmdch) {		/* diddle lines */
	    yank.lines = TRUE;
	    endp = nextline(TRUE, curr, count);
	    curr = bseekeol(curr);
	    disp = curr;
	}
	else {				/* diddle 'things' */
	    yank.lines = FALSE;
	    movecmd = movemap[(unsigned int)ch];

	    if ( (ok = (findCP(curr,&endp,movecmd) == LEGALMOVE)) ) {
		if (curr > endp) {
		    swap(&curr,&endp);
		    ok = (cmd != CHANGE_C);
		}
		if (adjcurr[(unsigned int)movecmd])
		    curr++;
		if (adjendp[(unsigned int)movecmd])
		    endp++;
	    }
	    if (!ok) {
		if (ch != ESC)
		    error();
		goto killredo;
	    }
	}
	
	endY = setY(endp);
	newend = curr;
	disp = curr;
	switch (cmd) {
	    case EXEC_C:
		ok = execute(curr, endp);
		break;
	    case DELETE_C:
		ok = deletion(curr, endp);
		break;
	    case ADJUST_C:
		adjuster((cmdch == '<'), endp-1, oldc);
		break;
	    case CHANGE_C:
		if (endp <= pend+1) {
		    mvcur(setY(endp-1), setX(endp-1));
		    printch('$');
		    mvcur(yp, xp);
		}
		if (deletion(curr, endp))
		    ok = ((newend = insertion(1, 0, &disp, &endY, TRUE)) >= 0);
		else
		    ok = FALSE;
		break;
	    case YANK_C:
		if (!doyank(curr, endp))
		    error();
		return;	/* xerox will not be true, nor will redoing */
	}

    }
    else {
	endp = curr;
	endY = yp;

	switch (cmd) {
	    case I_AT_NONWHITE:
	    case A_AT_END:
	    case APPEND_C:
	    case INSERT_C:		/* variations on insert */
		if (cmd != INSERT_C) {
		    if (cmd == APPEND_C)
			curr = min(curr+1, lend);
		    else if (cmd == A_AT_END)
			curr = lend;
		    else /* if (cmd == I_AT_NONWHITE) */
			curr = skipws(lstart);
		    xp = setX(curr);
		    mvcur(yp,xp);
		}
		newend = insertion(count, 0, &disp, &endY, TRUE);
		ok = (newend >= 0);
		break;
	    case OPEN_C:
	    case OPENUP_C:
		newend = insertion(1,setstep[ (cmd==OPENUP_C)&1 ],
						&disp,&endY,TRUE)-1;
		ok = (newend >= 0);
		break;
	    case REPLACE_C:
	    case TWIDDLE_C:
		if (cmd == REPLACE_C) {
		    if ((cmdch = readchar()) == ESC)
			goto killredo;
		}
		if (findCP(curr, &endp, GO_RIGHT) == LEGALMOVE)
		    squiggle(endp-1, cmdch, (cmd==REPLACE_C));
		break;
	    case PUT_BEFORE:
	    case PUT_AFTER:
		ok = put(cmd==PUT_AFTER);
		break;
	    case BIG_REPL_C:
		bigreplace();
		break;
	    case RESUBST_C:
		ok = FALSE;
		if (dst[0] != 0) {
		    newend = chop(curr, &lend, TRUE, &ok);
		    if (newend >= 0) {
			endY = setY(newend+strlen(dst));
			ok = TRUE;
		    }
		}
		break;
	    case JOIN_C:
		join(count);		/* join lines */
		break;
	    case UNDO_C:			/* undo last modification */
		ok = fixcore(&newend) >= 0;
		disp = newend;
		endY = MAGICNUMBER;
		break;
	}
    }

    if (ok) {
	setpos((newc<0)?newend:newc);
	setend();
	if (curr < ptop || curr > pend) {
	    yp = settop(12);
	    redisplay(TRUE);
	}
	else {
	    yp = setY(curr);
	    if (endY != setY(newend))	/* shuffled lines */
		refresh(setY(disp), setX(disp), disp, pend, TRUE);
	    else			/* refresh to end position */
		refresh(setY(disp), setX(disp), disp, newend, FALSE);
	}
	if (curr >= bufmax && bufmax > 0) {	/* adjust off end of buffer */
	    setpos(bufmax-1);
	    yp = setY(curr);
	}
	if (s_wrapped) {
	    prompt(FALSE, "search wrapped around end of buffer");
	    s_wrapped = 0;
	}
	else
	    clrprompt();
	modified = TRUE;
    }
    else {
	error();
killredo:
	rcb[0] = 0;
    }
    mvcur(yp, xp);
    if (xerox)
	*rcp = 0;	/* terminate the redo */
    redoing = FALSE;
    xerox = FALSE;
    core[bufmax] = EOL;
}

/* Initialize && execute a macro */

VOID PROC
exmacro()
{
    int mp;

    mp = lookup(ch);
    if (mp > 0) {
	if (macro<0)
	    zerostack(&undo);
	insertmacro(mbuffer[mp].m_text, count);
    }
    else
	error();
}

/* redraw the screen w.r.t. the cursor */

VOID PROC
zdraw(code)
unsigned char code;
{
    int nl = ERR,
	np = (count>0)?to_index(count):curr;

    if (movemap[code] == CR_FWD)
	nl = 0;
    else if (movemap[code] == CR_BACK)
	nl = LINES-1;
    else if (code == '.')
	nl = LINES / 2;
    if (nl >= 0) {
	curr = np;
	yp = settop(nl);
	redisplay(TRUE);
	mvcur(yp,xp);
    }
    else
	error();
}

/* start up a built-in macro */

VOID PROC
macrocommand()
{
    if (count > 1)
	numtoa(gcb,count);
    else
	gcb[0] = 0;
    switch (ch) { /* which macro? */
	case 'x':			/* x out characters */
	    strcat(gcb,"dl"); break;
	case 'X':			/* ... backwards */
	    strcat(gcb,"dh"); break;
	case 's':			/* substitute over chars */
	    strcat(gcb,"cl"); break;
	case 'D':			/* delete to end of line */
	    strcat(gcb,"d$"); break;
	case 'C':			/* change ... */
	    strcat(gcb,"c$"); break;
	case 'Y':			/* yank ... */
	    strcat(gcb,"y$"); break;
	case '':			/* scroll up one page */
	    strcpy(gcb,"22"); break;
	case '':			/* ... down one page */
	    strcpy(gcb,"22"); break;
	case '':			/* scroll up one line */
	    strcpy(gcb,"1"); break;
	case '':			/* ... down one line */
	    strcpy(gcb,"1"); break;
	default:
            error();
            return;
	break;
    }
    if (macro<0)
	zerostack(&undo);
    insertmacro(gcb, 1);
}

/* scroll the window up || down */

VOID PROC
scroll(down)
bool down;
{
    int i;

    if (count <= 0)
	count = dofscroll;
    strput(CURoff);
    if (down) {
	curr = min(bufmax-1, nextline(TRUE, curr, count));
	i = min(bufmax-1, nextline(TRUE, pend, count));
	if (i > pend)
	    scrollforward(i);
    }
    else {
	curr = bseekeol(max(0,nextline(FALSE, curr, count)));
	i = bseekeol(max(0,nextline(FALSE, ptop, count)));
	if (i < ptop) {
	    if (canUPSCROLL)
		scrollback(i);
	    else {
		ptop = i;
		setend();
		redisplay(TRUE);
	    }
	}
    }
    strput(CURon);
    setpos(skipws(curr));	/* initialize new position - first nonwhite */
    yp = setY(curr);
    mvcur(yp, xp);		/* go there */
}

exec_type PROC
editcore()
{
    cmdtype cmd;
    extern bool s_wrapped;
    
    /* rcb[0] = 0; rcp = rcb; */

    if (diddled) {
	setpos(skipws(curr));		/* set cursor x position.. */
	yp = settop(LINES / 2);		/* Y position */
    }
    if (diddled || zotscreen)		/* redisplay? */
	redisplay(FALSE);
    mvcur(yp, xp);			/* and move the cursor */

    for (;;) {
	s_wrapped = 0;
	ch = readchar();			/* get a char */
	count = 0;
	if (isdigit(ch) && ch != '0')
	    gcount();			/* ... a possible count */
	switch (cmd = movemap[(unsigned int)ch]) {
	  case FILE_C:
	    wr_stat();			/* write file stats */
	    mvcur(yp, xp);
	    break;

	  case WINDOW_UP:
	  case WINDOW_DOWN:
	    scroll(cmd==WINDOW_UP);		/* scroll the window */
	    break;

	  case REDRAW_C:			/* redraw the window */
	    redisplay(TRUE);
	    mvcur(yp, xp);
	    break;

	  case MARKER_C:			/* set a marker */
	    ch = tolower(readchar());
	    if (ch >= 'a' && ch <= 'z')
		contexts[ch-'`'] = curr;
	    else if (ch != ESC)
		error();
	    break;

	  case REDO_C:
	    if (rcb[0] != 0) {
		zerostack(&undo);
		insertmacro(rcb, 1);
		redoing = TRUE;
	    }
	    break;

	  case REWINDOW:
	    zdraw(readchar());		/* shift the window */
	    break;

	  case DEBUG_C:			/* debugging stuff -- unused */
	    break;

	  case ZZ_C:			/* shortcut for :xit */
	    ch = readchar();
	    if (ch == 'Z')
		insertmacro(":x\r", 1);
	    else if (ch != ESC)
		error();
	    break;

	  case EDIT_C:		/* drop into line mode */
	    return E_EDIT;

	  case COLIN_C:		/* do one exec mode command */
	    return E_VISUAL;

	  case HARDMACRO:
	    macrocommand();		/* 'hard-wired' macros */
	    break;

	  case SOFTMACRO:
	    exmacro();		/* run a macro */
	    break;

	  case INSMACRO:		/* macro for insert mode */
	  case BAD_COMMAND:
	    error();
	    break;

	  default:
	    if (cmd < DELETE_C)
		movearound(cmd);
	    else /*if (cmd < HARDMACRO)*/
		docommand(cmd);
	    break;
	}
	lastexec = 0;
    }
    /* never exits here */
}
