/*-
 * 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"ks.h"

#pragma noregalo

#define TABSTOP	4



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

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

	sprintf(buf,"Free%5d",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 *str )
{
	int x,xx;
	unsigned code;
	int size;
	int contflag;

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

		size = p->real_size + ((code & 0x8000) ? 2 : 1);
		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;

		p->real_size -= (*p->pos2 & 0x8000) ? 2 : 1;
		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 *str )
{
	int result;

	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 )
{
	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);
	}
}


KSTextArea *KSCreateTextArea( int x, int y, int width, int height, int maxsize, int (*callback)(KSTextArea *area, KSMsg *msg), void *call_data)
{
	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->cur_line = 1;
	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->callback = callback;
	p->call_data = call_data;
	p->column = 0;
	p->display = 1;

	return p;
}
