/* col.c
 * col [ -bfx ]
 */

#include <stdio.h>
#include <ctype.h>

#define ESC	  '\033'
 /* second chars of escape codes */
#define HALFDOWN  '9'
#define HALFUP	  '8'
#define WHOLEUP	  '7'

#define VT	  '\013' /* alternate form of full reverse lf */
#define SO	  '\017' /* turn on alternate char set */
#define SI	  '\016' /* turn it off */

#define CHUNKSIZE 80

/* globals */
int	backspace,	/* can terminal backspace? */
	half,		/* ok to do 1/2 forward lf? */
	squeeze,	/* turn white-space into tabs */
	col,		/* current column */
	whichalf,	/* are we on full line or between lines? */
	backs,		/* # of backspaces on current line */
	nextchunk,	/* next time we need to alloc space for current text */
	altset;		/* alternate character set mode */
	

struct line  /* double linked list */
{
	char *text;
	struct line *up;
	struct line *down;
	int updist;	/* one=1/2 lf, two=full lf */
	int downdist;
} *root,*linit(),*scan();

char *forward(),*strinsert();

main(argc,argv)
 int argc;
 char **argv;
{
	int x;
	char c,*argtmp;
	
	backspace=squeeze=1;
	whichalf=col=half=altset=backs=0;
 /* check args	*/
	if(argc>=2)
	  while(argtmp = *++argv)
		if(**argv=='-')
			while(c = *++*argv)
				switch(c)
				{

				case 'b':
				backspace=0;
					break;
				case 'f':
					half=1;
					break;
				case 'x':
					squeeze=0;
					break;
				default:
					fprintf(stderr,"col: Bad option %c\n"
					  ,c);
					exit(1);
				
				}
		else
		{
			fprintf(stderr,"col: Bad arg %s\n",argtmp);
			exit(1);
		}
		
	root=scan(NULL,NULL,NULL);  /* All nulls because so far there are
				     * no lines */
	printup(root);   /* print all lines above + including root */
	if(root)
		printdown(root->down); /* print all lines below root */
}


struct line *
scan(line,above,below)
 register struct line *line,*above,*below;
{
	int leg,coltmp,eol;
	char *charalloc(),*rightins();
	register char c;

	col -= backs*2;  /* get actual cursor pos on screen */
	backs=0;
	if(!line)
	{
		line=linit();
		line->text=charalloc(NULL,CHUNKSIZE);
		line->text[0]=NULL;
	}
	nextchunk=strlen(line->text)%CHUNKSIZE+CHUNKSIZE;
	coltmp=col;
	col=eol=0;
	line->text=forward(line->text,coltmp);
	while(!eol)
		switch(c=getchar())
		{
			
		case EOF:
			if(above)
			{
				above->down=line;
				line->up=above;
			}
			eol=1;
			break;
		case ESC:
			c=getchar(); /* find out what esc sequence is */
			switch(c)
			{

			case HALFDOWN:
				halfdown(line,above,below);
				eol=1;
				break;
			case HALFUP:
				halfup(line,above,below);
				eol=1;
				break;
			case WHOLEUP:
				wholeup(line,above,below);
				eol=1;
				break;
				
			}
			break;
		case '\r':
			col=backs=0;
			break;		
		case '\n':
			wholedown(line,above,below);
			break;
		case VT:
			wholeup(line,above,below);
			break;
		case '\t':
			/* convert to spaces */
			do
				line->text=rightins(line->text,' ');
			while((col-backs*2)%8);
			break;
		case '\b':
			if(col)
			{
				col--;
				while(line->text[col]=='\b')
					col=(col<2) ? 0:col-2;
			}
			break;
		case SO:
			altset=1;
			break;
		case SI:
			altset=0;
			break;
		default:
			if(isprint(c))
				line->text=rightins(line->text,c);
			break;

		}
	return(line);
}

halfdown(line,above,below)
 register struct line *line,*above,*below;
{
	whichalf=!whichalf;
	if(above)
	{
		line->updist=above->downdist;
		line->up=above;
		above->down=line;
	}
	if(half)
	{
		line->downdist=1;
		if(below)
		{
			below->up=line;
			if(below->updist==2)
			{
				below->updist=1;
				scan(NULL,line,below);
			}
			else /* below->updist==1 */
				scan(line->down=below,line,
				 below->down);
		}
		else
			scan(NULL,line,NULL);
	}
	else
	{
		if(below)
		{
			below->up=line;
			line->down=below;
			if(whichalf)
				scan(below,line,below->down);
			else
				scan(line,above,below);
		}
		else
		{
			if(whichalf)
				scan(NULL,line,NULL);
			else
				scan(line,above,NULL);
		}
	}
}	


halfup(line,above,below)  /* bascially the same as HALFDOWN */
 register struct line *line,*above,*below;
{
	whichalf=!whichalf;
	if(below)
	{
		line->downdist=below->updist;
		line->down=below;
		below->up=line;
	}
	if(half)
	{
		line->updist=1;
		if(above)
		{
			above->down=line;
			if(above->downdist==2)
			{
				above->downdist=1;
				scan(NULL,above,line);
			}
			else  /* above->downdist==1 */
				scan(line->up=above,above->up,line);
		}
		else
			scan(NULL,NULL,line);
	}
	else
	{
		if(above)
		{
			above->down=line;
			line->up=above;
			if(whichalf)
				scan(line,above,below);
			else
				scan(above,above->up,line);
		}
		else
		{
			if(whichalf)
				scan(line,NULL,below);
			else
				scan(NULL,NULL,line);
		}
	}
}
	

wholedown(line,above,below)
 register struct line *line,*above,*below;
{
	col=0;  /* go to beginning of line */
	if(above)
	{
		line->updist=above->downdist;
		line->up=above;
		above->down=line;
	}
	if(half)
	{
		if(below)
		{
			line->down=below;
			line->downdist=below->updist;
			below->up=line;
			if(line->downdist==2)
				scan(below,line,below->down);
			else  /* line->downdist==1 */
			{
				switch(below->downdist) /* next below*/
				{
					
				case 0: /* nothing down there */
					below->downdist=1;
					scan(NULL,below,NULL);
					break;
				case 1: /* 1/2 line, exactly what we
					 * need */
					scan(below->down,below,
					 below->down->down);
					break;
				case 2: /* put line between below and
					 * below->down */
					below->downdist=1;
					below->down->updist=1;
					scan(NULL,below,below->down);
					break;
					
				}
			}
		}
		else /* create line 2 halves down from 'line' */
		{
			
			line->downdist=2;
			scan(NULL,line,NULL);
		}
	}
	else
	{
		if(below)
		{
			line->down=below;
			below->up=line;
			scan(below,line,below->down);
		}
		else
			scan(NULL,line,NULL);
	}
}


wholeup(line,above,below)  /* bascially same as WHOLEDOWN */
 register struct line *line,*above,*below;
{
	if(below)
	{
		line->downdist=below->updist;
		line->down=below;
		below->up=line;
	}
	if(half)
	{
		if(above)
		{
			line->up=above;
			line->updist=above->downdist;
			above->down=line;
			if(line->updist==2)
				scan(above,above->up,line);
			else /* line->updist==1 */
				switch(above->updist) /* next above */
				{
					
				case 0: /* nothing up there */
					above->updist=1;
					scan(NULL,NULL,above);
					break;
				case 1: /* 1/2 line, perfect */
					scan(above->up,above->up->up,
					 above);
					break;
				case 2: /* squeeze a line between
					 * above->up and above */
					above->updist=1;
					above->up->downdist=1;
					scan(NULL,above->up,above);
					break;
					
					}
		}
		else /* create line 2 halves up from 'line' */
		{
			line->updist=2;
			scan(NULL,NULL,line);
		}
	}
	else
	{
		if(above)
		{
			line->up=above;
			above->down=line;
			scan(above,above->up,line);
		}
		else
			scan(NULL,NULL,line);
	}
}


char *
rightins(s,c) /* choose the right way to insert c into s[col] */
 register char *s,c;
{
	c=altset ? c|0200 : c&0177; /* use high bit as flag to tell whether or
				     * not char is in alt character set */
	if(!s[col])
	{
		if(col+2>=nextchunk)
			s=charalloc(s,nextchunk+=CHUNKSIZE);
		s[col++]=c;
		s[col]=NULL;
		return(s);
	}
	else
	{
			if(s[col]==' ' || !backspace) /* overwrite it */
				s[col]=c;
			else if(c!=' ') /* no use inserting a space */
			{
				++col;
				s=strinsert(s,col++,'\b');
				s=strinsert(s,col,c);
				backs++;
			}
			return(forward(s,1));
	}
}


struct line *
linit() /* alloc line structure and nullify all members */
{
	char *malloc();
	struct line *line;

	line=(struct line *)charalloc(NULL,sizeof(struct line));
	line->up=line->down=(struct line *)NULL;
	line->text=(char *)NULL;
	line->updist=line->downdist=0;
	return(line);
}


char *
charalloc(ptr,nbytes)  /* if ptr==NULL, allocate new memory, if !=NULL
			* realloc. ; then check for errors */
 char *ptr;
 int nbytes;
{
	char *malloc(),*realloc();
	
	if(ptr==NULL)
		ptr=malloc(nbytes);
	else
		ptr=realloc(ptr,nbytes);
	if(ptr==NULL)
		error("Can't allocate any more memory");
	return(ptr);
}


printup(line)  /* recursively print lines above & including 'line' */
 struct line *line;
{
	if(line) /* exit clause */
	{
		printup(line->up);
		printline(line);
	}
}


printdown(line)  /* recursively print lines below & including 'line'*/
 struct line *line;
{
	if(line)
	{
		printline(line);
		printdown(line->down);
	}
}


printline(line) /* print line->text and appropriate lf */
 register struct line *line;
{
	int anytabs,t;
	int pos; /* used for space squeezing */
	register char c;
	register int i,spaces;
	static int inalt=0;

/* i is used to point to element of line->text being printed out, col is the
 * cursor column
 */
	spaces=0;
	for(i=col=0; c=line->text[i]; i++,col++)
	{
		if(c&0200)
		{
			if(!inalt)
			{
				inalt=1;
				putchar(SO);
			}
		}
		else
			if(inalt)
			{
				inalt=0;
				putchar(SI);
			}
		c &= 0177; /* chop off high bit */
		if(squeeze)
		{
			switch(c)

			{
			case ' ':
				++spaces;
				break;
			case '\b':
				col--;
				break;
			default:
				if(spaces)
				{
					pos=col-spaces;
					anytabs=t=((col-spaces)%8+spaces)/8;
					while(t--)
					{
						if(pos%8<7) /* make sure tab
							     * isn't printed
							     * for just one
							     * space
							     */
							putchar('\t');
						else
							putchar(' ');
						pos=0; /* flag so next time it
							* will always print tab
							*/
					}
					if(anytabs)
						spaces=i%8;
					while(spaces--)
						putchar(' ');
					spaces=0;
				}
				break;

			}
			if(c!=' ')
				putchar(c);
		}
		else
			putchar(c);		
	}
	if(half)
		switch(line->downdist)
		{

		case 0: /* last line of text */
			if(i>0)
				if(half && whichalf)
					printf("\033\071\r");
				else
					putchar('\n');
			break;
		case 1:
			printf("\033\071\r");
			break;
		case 2:
			putchar('\n');
			break;

		}
	else if(i>0 || line->down!=NULL) /* no \n if last line is blank */
		putchar('\n');
}


char *
strinsert(str,in,c)  /* insert c at str[in] */
 register char *str,c;
 int in;
{
	int foo;
	register char tmp,*ptr;
	
	ptr=str+in;
	while(*ptr)
	{
		tmp = *ptr;
		*ptr++ = c;
		c=tmp;
	}
	if((ptr-str+2)>nextchunk)
	{
		str=charalloc(str,nextchunk+=CHUNKSIZE); /* alloc text space in
						   * chunks of CHUNKSIZE, it's
						   * faster than one by one
						   */
		ptr=str+foo-2;
	}
	*ptr=c;
	*++ptr='\0';
	return(str);
}


char *
forward(s,n) /* move forward n chars in s counting \b as moving back 1 */
 char *s;
 int n;
{
	register int i;

	for(i=0; i<n || s[col]=='\b'; i++, col++)
	{
		if(!s[col])
			s=strinsert(s,col,' ');
		else if(s[col]=='\b' && i)
			i--;
	}
	return(s);
}


error(s,c) /* print error and exit */
{
	fprintf(stderr,"col: %s %c\n",s,c);
	exit(1);
}

