/*-
 * Copyright (c) 2001
 * Tatsuya Kudoh(CDR/TK),ROYALPANDA.    All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 */

#include<stdlib.h>
#include<stdio.h>
#include<sys/bios.h>
#include<fcntl.h>

#include"ks.h"

#pragma noregalo

#define TABSTOP	4

#define MAXSIZE		(1024*20)	/* max characters in buffer */

static int NewFileSize = 8192/128;

#define NEWLINE_CRLF	1	/* set 0 or 1 */


static void show_capacity( KSTextArea *p )
{
	char buf[16];
	int i,x,y;
	unsigned freemem;

	freemem = p->real_max_size - p->real_size;

	sprintf(buf,"Free%5u",freemem);
	x = p->win.width - 10;
	y = p->win.height - 1;
	i = 0;
	while( buf[i] )
		KSDrawCharacter(&p->win,x++,y,buf[i++],KS_PLT_GRAY);
}



static void redraw_backward( KSTextArea *p )
{
	int x,y;
	unsigned *pos;
	int c;
	unsigned plt;

	x = p->cx;
	y = p->cy;
	pos = p->pos1;

	for(;;){
		if( p->mark && p->mark < pos )
			plt = KS_PLT_EMPH;
		else
			plt = KS_PLT_NORMAL;

		pos--;
		x--;
		if( x < 0 ){
			x = p->width - 1;
			y--;
			if( y < 0 )
				break;
		}
		if( pos < p->buf )
			break;

		c = *pos & 0xff;
		if( c == KEYCODE_TAB || c == KEYCODE_RETURN ){
			c = *pos >> 8;
			while( c-- ){
				KSDrawCharacter(&p->win,x+1,y+1,32,plt);
				x--;
			}
			x++;
		}else{
			KSDrawCharacter(&p->win,x+1,y+1,*pos,plt);
		}
	}
}

static void redraw_forward( KSTextArea *p )
{
	int x,y;
	unsigned *pos;
	int c;
	unsigned plt;

	x = p->cx;
	y = p->cy;

	pos = p->pos2;
	for(;;){
		if( p->mark && p->mark > pos )
			plt = KS_PLT_EMPH;
		else
			plt = KS_PLT_NORMAL;

		if( pos < p->buf + p->max_size ){
			c = *pos & 0xff;
			if( c == KEYCODE_TAB || c == KEYCODE_RETURN ){
				c = *pos >> 8;
				while( c-- ){
					KSDrawCharacter(&p->win,x+1,y+1,32,plt);
					x++;
				}
			}else{
				KSDrawCharacter(&p->win,x+1,y+1,*pos,plt);
				x++;
			}
			pos++;
		}else{
			KSDrawCharacter(&p->win,x+1,y+1,32,plt);
			x++;
		}
		if( x >= p->width ){
			x = 0;
			y++;
			if( y >= p->height )
				break;
		}
	}
}


static int redraw_handler( KSMsg *msg )
{
	KSTextArea *p;

	if( msg->type != KS_MSG_EXPOSURE )
		return 0;

	p = msg->exposure.call_data;
	KSDrawWindowBorder(&p->win);
	show_capacity(p);
	redraw_backward(p);
	redraw_forward(p);

	return 0;
}


static void cursor( KSTextArea *p )
{
	KSMoveCursor(p->win.x+p->cx+1,p->win.y+p->cy+1);
}


static int forward( KSTextArea *p )
{
	unsigned c;

	if( p->pos2 == p->buf + p->max_size )
		return 0;

	if( p->mark == p->pos1 || p->mark == p->pos2 )
		p->mark = p->pos1;

	c = *p->pos2++;
	*p->pos1++ = c;

	if( (c & 0xff) == KEYCODE_RETURN || (c & 0xff) == KEYCODE_TAB )
		p->cx += c >> 8;
	else
		p->cx++;

	if( p->cx >= p->width ){
		p->cx = 0;
		p->cy++;
	}

	p->column = p->cx;

	return 1;
}

static int backward( KSTextArea *p )
{
	unsigned c;

	if( p->pos1 == p->buf )
		return 0;

	if( p->mark == p->pos1 || p->mark == p->pos2 )
		p->mark = p->pos2;

	p->pos1--;
	p->pos2--;
	c = *p->pos1;
	*p->pos2 = c;

	if( (c & 0xff) == KEYCODE_RETURN || (c & 0xff) == KEYCODE_TAB )
		p->cx -= c >> 8;
	else
		p->cx--;

	if( p->cx < 0){
		p->cy--;
		p->cx += p->width;
	}
	p->column = p->cx;
	return 1;
}


static int nextline( KSTextArea *p )
{
	int y;
	int c;
	int column;

	column = p->column;
	y = p->cy;
	c = 0;
	while( p->cy == y ){
		if( forward(p) == 0 )
			break;
		c++;
	}
	if( p->cy == y ){
		while( c-- )
			backward(p);
		return 0;
	}
	while( p->cx < column ){
		if( p->pos2 == p->buf + p->max_size )
			break;
		if( (*p->pos2 & 0xff) == KEYCODE_RETURN )
			break;
		forward(p);
	}
	p->column = column;
	return 1;
}


static int prevline( KSTextArea *p )
{
	int y;
	int c;
	int column;

	column = p->column;
	y = p->cy;
	c = 0;
	while( p->cy == y ){
		if( backward(p) == 0 )
			break;
		c++;
	}
	if( p->cy == y ){
		while( c-- )
			forward(p);
		return 0;
	}
	while( p->cx > column )
		backward(p);
	p->column = column;
	return 1;
}


static void calculate_tablength( KSTextArea *p )
{
	unsigned *pos;
	int xx,x;

	x = p->cx;
	pos = p->pos2;
	while( pos < p->buf + p->max_size ){
		xx = x;
		if( (*pos & 0xff) == KEYCODE_RETURN ){
			*pos = (p->width - xx) << 8 | KEYCODE_RETURN;
			return;
		}else if( (*pos & 0xff) == KEYCODE_TAB ){
			x = (x/TABSTOP + 1) * TABSTOP;
			*pos++ = (x - xx) << 8 | KEYCODE_TAB;
		}else{
			x++;
			pos++;
		}
		if( x >= p->width )
			x = 0;
	}
}


static int insert( KSTextArea *p, unsigned far *str )
{
	int x,xx;
	unsigned code;
	unsigned size;
	int contflag;

	contflag = 1;
	x = p->cx;
	while( *str ){
		xx = x;
		code = *str++;

		if( code & 0x8000 ){
			size = p->real_size + 2;
		}else{
			code &= 0xff;
#if NEWLINE_CRLF
			if( code == KEYCODE_RETURN )
				size = p->real_size + 2;
			else
				size = p->real_size +1;
#else
			size = p->real_size + 1;
#endif
		}
		if( size > p->real_max_size ){
			contflag = 0;
			break;
		}

		p->real_size = size;

		if( code == KEYCODE_RETURN ){
			x = p->width;
			code = (x - xx) << 8 | KEYCODE_RETURN;
		}else if( code == KEYCODE_TAB ){
			x = (x/TABSTOP + 1) * TABSTOP;
			code = (x - xx) << 8 | KEYCODE_TAB;
		}else{
			x++;
		}
		if( x >= p->width ){
			x = 0;
			p->cy++;
		}
		*p->pos1++ = code;
	}
	p->cx = x;
	calculate_tablength(p);
	p->column = p->cx;

	return contflag;
}


static int deletebackward( KSTextArea *p, int length )
{
	if( p->pos1 == p->buf )
		return 0;

	while( length-- ){
		if( backward(p) == 0 )
			break;

#if NEWLINE_CRLF
		if( (*p->pos2 & 0x8000)||(*p->pos2 & 0xff)==KEYCODE_RETURN )
			p->real_size -= 2;
		else
			p->real_size--;
#else
		p->real_size -= (*p->pos2 & 0x8000) ? 2 : 1;
#endif
		p->pos2++;
	}

	calculate_tablength(p);
	p->column = p->cx;

	return 1;
}


void KSTextAreaRedraw( KSTextArea *p )
{
	redraw_backward(p);
	redraw_forward(p);
	cursor(p);
}



int KSTextAreaForward( KSTextArea *p )
{
	int redraw;

	if( forward(p) == 0 ){
		cursor(p);
		return 0;
	}

	redraw = p->mark ? 1 : 0;
	if( p->cy >= p->height ){
		p->cy = p->height - 1;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_forward(p);
			redraw_backward(p);
		}
		cursor(p);
	}
	return 1;
}

int KSTextAreaBackward( KSTextArea *p )
{
	int redraw;

	if( backward(p) == 0 ){
		cursor(p);
		return 0;
	}

	redraw = p->mark ? 1 : 0;
	if( p->cy < 0 ){
		p->cy = 0;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_forward(p);
			redraw_backward(p);
		}
		cursor(p);
	}
	return 1;
}


int KSTextAreaNextLine( KSTextArea *p )
{
	int redraw;

	if( nextline(p) == 0 ){
		cursor(p);
		return 0;
	}

	redraw = p->mark ? 1 : 0;
	if( p->cy >= p->height ){
		p->cy = p->height - 1;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_forward(p);
			redraw_backward(p);
		}
		cursor(p);
	}
	return 1;
}


int KSTextAreaPrevLine( KSTextArea *p )
{
	int redraw;

	if( prevline(p) == 0 ){
		cursor(p);
		return 0;
	}

	redraw = p->mark ? 1 : 0;
	if( p->cy < 0 ){
		p->cy = 0;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_backward(p);
			redraw_forward(p);
		}
		cursor(p);
	}
	return 1;
}


int KSTextAreaPageUp( KSTextArea *p )
{
	int height;
	int redraw;

	height = p->height - 1;
	while( height-- ){
		if( prevline(p) == 0 )
			break;
	}
	redraw = p->mark ? 1 : 0;
	if( p->cy < 0 ){
		p->cy = 0;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_forward(p);
			redraw_backward(p);
		}
		cursor(p);
	}
	return 1;
}

int KSTextAreaPageDown( KSTextArea *p )
{
	int height;
	int redraw;

	height = p->height - 1;
	while( height-- ){
		if( nextline(p) == 0 )
			break;
	}

	redraw = p->mark ? 1 : 0;
	if( p->cy >= p->height ){
		p->cy = p->height - 1;
		redraw = 1;
	}
	if( p->display ){
		if( redraw ){
			redraw_forward(p);
			redraw_backward(p);
		}
		cursor(p);
	}
	return 1;
}


int KSTextAreaLineHead( KSTextArea *p )
{
	while( p->cx > 0 )
		backward(p);

	if( p->display ){
		if( p->mark )
			redraw_forward(p);
		cursor(p);
	}
	return 1;
}

int KSTextAreaLineEnd( KSTextArea *p )
{
	int y;

	y = p->cy;
	while( p->cy == y ){
		if( forward(p) == 0 )
			break;
	}
	if( p->cy != y )
		backward(p);

	if( p->display ){
		redraw_backward(p);
		cursor(p);
	}
	return 1;
}


int KSTextAreaInsert( KSTextArea *p, unsigned far *str )
{
	int result;

	p->update_flag = 1;

	result = insert(p,str);

	if( p->cy >= p->height )
		p->cy = p->height-1;

	if( p->display ){
		show_capacity(p);
		redraw_backward(p);
		redraw_forward(p);
		cursor(p);
	}
	return result;
}


int KSTextAreaDeleteBackward( KSTextArea *p, int length )
{
	p->update_flag = 1;

	if( deletebackward(p,length) == 0 ){
		cursor(p);
		return 0;
	}
	if( p->cy < 0 )
		p->cy = 0;
	if( p->display ){
		show_capacity(p);
		redraw_backward(p);
		redraw_forward(p);
		cursor(p);
	}
	return 1;
}


void KSTextAreaDisplay( KSTextArea *p,int disp )
{
	p->display = disp;
	if( p->display ){
		show_capacity(p);
		redraw_backward(p);
		redraw_forward(p);
		cursor(p);
	}
}


void KSTextAreaSetMark( KSTextArea *p )
{
	p->mark = p->pos1;
}

void KSTextAreaResetMark( KSTextArea *p )
{
	if( p->mark ){
		p->mark = NULL;
		redraw_backward(p);
		redraw_forward(p);
	}
}


unsigned *KSTextAreaCopyRegion( KSTextArea *p )
{
	int len;
	unsigned *s,*d;
	unsigned *np;

	if( p->mark == NULL )
		return NULL;

	if( p->mark == p->pos1 || p->mark == p->pos2 )
		return NULL;

	if( p->mark < p->pos1 ){
		s = p->mark;
		len = p->pos1 - p->mark;
	}else if( p->mark > p->pos2 ){
		s = p->pos2;
		len = p->mark - p->pos2;
	}

	np = KSAllocate(len*2+2);
	if( np == NULL )
		return NULL;

	d = np;
	while( len-- )
		*d++ = *s++;
	*d = 0;

	return np;
}


void KSTextAreaDeleteRegion( KSTextArea *p )
{
	int len;
	int l;

	if( p->mark == NULL )
		return;

	if( p->mark == p->pos1 || p->mark == p->pos2 )
		return;

	if( p->mark < p->pos1 ){
		len = p->pos1 - p->mark;
	}else if( p->mark > p->pos2 ){
		len = p->mark - p->pos2;
		l = len;
		while( l-- )
			forward(p);
	}
	KSTextAreaDeleteBackward(p,len);
}



KSTextArea *KSTextAreaCreate( int x, int y, int width, int height, int maxsize)
{
	KSTextArea *p;

	p = KSAllocate(sizeof(KSTextArea) + maxsize*2);
	if( p == NULL )
		return NULL;

	width -= width % TABSTOP;

	p->win.x = x;
	p->win.y = y;
	p->win.width = width + 2;
	p->win.height = height + 2;
	p->win.mapped = 1;
	p->win.receiver = redraw_handler;
	p->win.call_data = p;

	p->width = width;
	p->height = height;

	p->cx = 0;
	p->cy = 0;
	p->real_max_size =maxsize;
	p->real_size = 0;
	p->max_size = maxsize;
	p->pos1 = p->buf;
	p->pos2 = p->buf + p->max_size;
	p->mark = NULL;
	p->column = 0;
	p->display = 1;
	p->update_flag = 0;

	return p;
}


KSTextArea *KSTextAreaOpenFile( int x, int y, int width, int height, unsigned char far *filename )
{
	struct stat statbuf;
	unsigned size;
	unsigned len,l;
	KSFile *fp;
	KSTextArea *p;
	unsigned real_size;
	int cx,xx;
	unsigned *s;

	if( stat(filename,&statbuf) == E_FS_SUCCESS ){
		if( statbuf.count > MAXSIZE/128 )
			size = MAXSIZE;
		else
			size = statbuf.count * 128;
	}else{
		if( creat(filename,6,NewFileSize) != E_FS_SUCCESS )
			return NULL;
		if( NewFileSize > MAXSIZE/128 )
			size = MAXSIZE;
		else
			size = NewFileSize * 128;

		return KSTextAreaCreate(x,y,width,height,size);
	}

	p = KSTextAreaCreate(x,y,width,height,size);
	if( p == NULL )
		return NULL;

	fp = KSFileOpen(filename,KS_FILE_READ);
	if( fp == NULL ){
		KSFree(p);
		return NULL;
	}

	len = KSFileRead(fp,p->buf,size);
	KSFileClose(fp);

	s = p->buf;
	cx = 0;
	real_size = 0;
	l = len;
	while( l-- ){
		xx = cx;
		if( *s & 0x8000 ){
			real_size += 2;
			cx++;
		}else{
			*s &= 0xff;
#if 1
			if( *s == KEYCODE_RETURN ){
				cx = p->width;
				*s = (cx - xx) << 8 | KEYCODE_RETURN;
#if NEWLINE_CRLF
				real_size++;
#endif
			}else if( *s == KEYCODE_TAB ){
				cx = (cx/TABSTOP + 1) * TABSTOP;
				*s = (cx - xx) << 8 | KEYCODE_TAB;
			}else{
				cx++;
			}
#else
				cx++;
#endif
			real_size++;
		}
		if( cx == p->width )
			cx = 0;
		s++;
	}
	p->real_size = real_size;

	p->pos1 += len;
	while( p->pos1  > p->buf ){
		p->pos1--;
		p->pos2--;
		*p->pos2 = *p->pos1;
	}

	return p;
}


int KSTextAreaSetNewFileSize( int count )
{
	int c;

	c = NewFileSize;
	NewFileSize = count;
	return c;
}


int KSTextAreaWriteFile( KSTextArea *p, unsigned char far *filename )
{
	KSFile *fp;
	int len;

#if NEWLINE_CRLF
	fp = KSFileOpen(filename,KS_FILE_WRITE|KS_FILE_CRLF);
#else
	fp = KSFileOpen(filename,KS_FILE_WRITE);
#endif
	if( fp == NULL ){
		KSWriteStringToConsole("Open error\n");
		return -1;
	}

	len = p->pos1 - p->buf;
	if( len > 0 ){
		if( KSFileWrite(fp,p->buf,len) < len ){
			KSWriteStringToConsole("Write error\n");
			return -1;
		}
	}

	len = p->buf + p->max_size - p->pos2;
	if( len > 0 ){
		if( KSFileWrite(fp,p->pos2,len) < len ){
			KSWriteStringToConsole("Write error\n");
			return -1;
		}
	}
	KSFileClose(fp);
	p->update_flag = 0;

	return 0;
}


void KSTextAreaShowCursor( KSTextArea *p )
{
	cursor(p);
}
