/*
 * LEVEE, or Captain Video;  A vi clone
 *
 * Copyright (c) 1982-2007 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"

int PROC findcol();
int PROC moveword();
int PROC sentence();
int PROC match();
int PROC fchar(), bchar();

/* driver for movement commands */

findstates PROC
findCP(curp,newpos,cmd)
int curp, *newpos;
cmdtype cmd;
{
    static char chars[2] = {'/','?'};
    char tsearch;

    *newpos = ERR;
    switch (cmd) {			/* move around */
    
    case GO_LEFT:
	*newpos = max(lstart, curp-max(count,1));
	break;

    case GO_RIGHT:
	*newpos = min(lend, curp+max(count,1));
	break;

    case GO_UP:
    case GO_DOWN:
	*newpos = nextline(cmd==GO_DOWN, curp, count);
	if (*newpos >= 0 && *newpos < bufmax)
	    *newpos = findcol(*newpos,xp);
	break;

    case FORWD:
    case TO_WD:
    case BACK_WD:
    case BTO_WD:
	*newpos = moveword(curp,(cmd <= TO_WD),(cmd==TO_WD || cmd==BTO_WD));
	break;

    case NOTWHITE:
	*newpos = skipws(bseekeol(curp));
	break;

    case TO_COL:
	*newpos = findcol(curp, count);
	break;

    case TO_EOL:
	while ( (count-- > 1) && (curp < bufmax) )
	    curp = 1+fseekeol(curp);
	*newpos = fseekeol(curp);
	break;

    case PARA_FWD:
	do
	    curp = findfwd("^*[ \t$",curp+1,bufmax-1);
	while (curp != ERR && --count > 0);
	*newpos = (curp==ERR)?bufmax:curp;
	break;

    case PARA_BACK:
	do
	    curp = findback("^*[ \t$",curp-1,0);
	while (curp != ERR && --count > 0);
	*newpos = (curp==ERR)?0:curp;
	break;

    case SENT_FWD:
    case SENT_BACK:
	*newpos = sentence(curp, cmd==SENT_FWD);
	break;

    case MATCHEXPR:
	*newpos = match(curp);
	break;

    case TO_CHAR:
    case UPTO_CHAR:
    case BACK_CHAR:
    case BACKTO_CHAR:
	ch = readchar();
	if (ch == ESC)
	    return ESCAPED;
	if (cmd<=UPTO_CHAR) {
	    *newpos = fchar(curp,*newpos);
	    if (cmd==UPTO_CHAR && *newpos>=0)
		*newpos = max(curp, *newpos-1);
	}
	else {
	    *newpos = bchar(curp,*newpos);
	    if (cmd==BACKTO_CHAR && *newpos>=0)
		*newpos = min(curp, *newpos+1);
	}
	break;

    case PAGE_BEGIN:
	*newpos = ptop;
	break;

    case PAGE_END:
	*newpos = pend;
	break;

    case PAGE_MIDDLE:
	curp = ptop;
	count = 12;
	while (count-- > 0 && curp < bufmax)
	   curp = 1+fseekeol(curp);
	*newpos = skipws(curp);
	break;

    case GLOBAL_LINE:
	if (count <= 0)
	    *newpos = bufmax-1;
	else
	    *newpos = to_index(count);
	break;

    case TO_MARK:
    case TO_MARK_LINE:
	*newpos = getcontext((char)tolower(readchar()), cmd==TO_MARK_LINE);
	break;

    case CR_FWD:
    case CR_BACK:
	curp = nextline(cmd==CR_FWD, curp, count);
	if (cmd==CR_BACK && curp > 0)
	    curp = bseekeol(curp);
	*newpos = skipws(curp);
	break;

    case PATT_FWD:
    case PATT_BACK:			/* search for pattern */
    case FSEARCH:
    case BSEARCH:
	clrprompt();
	if (cmd == PATT_FWD || cmd == PATT_BACK) {
	    printch(tsearch = instring[0] = chars[cmd-PATT_FWD]);
	    if (!getline(&instring[1]))
		return ESCAPED;	/* needs to skip later tests */
	}
	else {
	    if (!lsearch)
		return BADMOVE;
	    tsearch = lsearch;
	    printch(instring[0] = (cmd==FSEARCH)?lsearch:((lsearch=='?')?'/':'?') );
	    prints(lastpatt);
	    instring[1] = 0;
	}
	if (*findparse(instring, newpos, curp)) { /* croaked patt */
	    *newpos = ERR;
	    prompt(TRUE,"bad pattern");
	}
	else if (*newpos == ERR)
	    prompt(FALSE,"no match");
	lsearch = tsearch;	/* fixup for N, n */
	break;
    }
    if ( ((*newpos) >= 0) && ((*newpos) <= bufmax) )
	return LEGALMOVE;
    return BADMOVE;
}

/* this procedure handles all movement in visual mode */

VOID PROC
movearound(cmd)
cmdtype cmd;
{
    int cp;
    
    switch (findCP(curr, &cp, cmd)) {
	case LEGALMOVE:
	    if (cp < bufmax) {
		if (cmd >= PATT_FWD)		/* absolute move */
		    contexts[0] = curr;	/* so save old position... */
		curr = cp;			/* goto new position */
		if (cmd==GO_UP || cmd==GO_DOWN) /* reset Xpos */
		    deranged = TRUE;
		else
		    xp = setX(cp);		/* just reset XP */
		if (cp < lstart || cp > lend) {
		    lstart = bseekeol(curr);
		    lend   = fseekeol(curr);
		    if (curr < ptop) {
			if (canUPSCROLL && ok_to_scroll(curr, ptop)) {
			    scrollback(curr);
			    yp = 0;
			}
			else {
			    yp = settop(LINES / 2);
			    redisplay(TRUE);
			}
		    }
		    else if (curr > pend) {
			if (ok_to_scroll(pend, curr)) {
			    scrollforward(curr);
			    yp = LINES-2;
			}
			else {
			    yp = settop(LINES / 2);
			    redisplay(TRUE);
			}
		    }
		    else
			yp = setY(curr);
		}
	    }
	    else
		error();
	break;
	case BADMOVE:
	    error();
	break;
    }
    mvcur(yp, xp);
}
    
int PROC
findcol(ip, col)
int ip, col;
{
    int tcol, endd;
    
    ip = bseekeol(ip);			/* start search here */
    endd = fseekeol(ip);		/* end search here */

    tcol = 0;
    while (tcol < col && ip < endd)
	switch (cclass(core[ip++])) {
	    case 0: tcol++;    break;
	    case 1: tcol += 2; break;
	    case 3: tcol += 3; break;
	    case 2: tcol = tabsize*(1+(tcol/tabsize)); break;
	}
    return(ip);
}

char dstpatt[]="[](){}", srcpatt[]="][)(}{";

/* find matching [], (), {} */

int PROC
match(p)
int p;
{
    char srcchar, dstchar;
    int lev, step;
    
    while((lev = scan(6,'=',core[p],srcpatt)) >= 6 && core[p] != EOL)
	p++;
    if (lev < 6) {
	srcchar = srcpatt[lev];
	dstchar = dstpatt[lev];
	step = setstep[lev&1];
	lev = 0;
	while (p >= 0 && p < bufmax) {
	    p += step;
	    if (core[p] == srcchar)
		lev++;
	    else if (core[p] == dstchar)
		if(--lev < 0)
		    return p;
	}
    }
    return (-1);
}

char * PROC
class(c)
/* find the character class of a char -- for word movement */
char c;
{
    if (strchr(wordset,c))
	return wordset;
    else if (strchr(spaces,c))
	return spaces;
    else
	return (char*)NULL;
}

int PROC
skip(chars,cp,step)
/* skip past characters in a character class */
char *chars;
register int cp;
int step;
{
    while (cp >= 0 && cp < bufmax && strchr(chars,core[cp]))
	cp += step;
    return cp;
}

int PROC
tow(cp,step)
/* skip to the start of the next word */
register int cp;
register int step;
{
    while (cp >= 0 && cp < bufmax
		 && !(strchr(wordset,core[cp]) || strchr(spaces,core[cp])))
	cp += step;
    return cp;
}

int PROC
moveword(cp,forwd,toword)
/* word movement */
int cp;
bool forwd, toword;
{
    int step;
    char *ccl;
    
    step = setstep[forwd];	/* set direction to move.. */
    if (!toword)
	cp += step;		/* advance one character */
    count = max(1,count);
    ccl = class(core[cp]);
    if (toword && ccl == spaces) {	/* skip to start of word */
	count--;
	cp = skip(spaces,cp,step);
	ccl = class(core[cp]);
    }

    while (cp >= 0 && cp < bufmax && count-- > 0) {
	if (ccl == spaces) {
	    cp = skip(spaces,cp,step);
	    ccl = class(core[cp]);
	}
	cp = (ccl)?skip(ccl,cp,step):tow(cp,step);
	ccl = class(core[cp]);
    }
    if (toword) {				/* past whitespace? */
	if (ccl == spaces)
	    cp = skip(spaces,cp,step);
	return cp;
    }
    return cp-step;				/* sit on last character. */
}

/* find a character forward on current line */

int PROC
fchar(pos,npos)
int pos,npos;
{
    do
	pos += scan(lend-pos-1,'=',ch, &core[pos+1]) + 1;
    while (--count>0 && pos<lend);
    if (pos<lend)
	return(pos);
    return(npos);
}

/* find a character backward on the current line */

int PROC
bchar(pos,npos)
int pos,npos;
{
    do
	pos += scan(-pos+lstart+1,'=',ch, &core[pos-1]) - 1;
    while (--count>0 && pos>=lstart);
    if (pos>=lstart)
	return(pos);
    return(npos);
}

/* look for the end of a sentence forward */

int PROC
ahead(i)
int i;
{
    char c;

    do {
	if ((c=core[i]) == '.' || c == '?' || c == '!')
	    if (i == bufmax-1 || (c=core[1+i]) == TAB || c == EOL || c == ' ')
		return i;

    } while (++i < bufmax);
    return -1;
}

/* look for the end of a sentence backwards. */

int PROC
back(i)
int i;
{
    char c;

    do {
	if ((c=core[i]) == '.' || c == '?' || c == '!')
	    if ((c=core[1+i]) == TAB || c == EOL || c == ' ')
		return i;

    } while (--i >= 0);
    return -1;
}

/* find the end of the next/last sentence.
    Sentences are delimited by ., !, or ? followed by a space.
*/

int PROC
sentence(start,forwd)
int start;
bool forwd;
{
    do {
	if (forwd)
	    start = ahead(start+1);
	else
	    start = back(start-1);
    } while (--count > 0 && start >= 0);
    return start;
}
