/*****************************************************************************/
/* Mini Screen Edit SYSTEM for X68000 V1.00 Copyright (C) 1994 by AIG-SOFT   */
/*****************************************************************************/

#include <iocslib.h>
#include "defines.h"
#include "abuff.h"
#include "amacro.h"
#include "akey.h"

/*****************************************************************************/
/* 内部変数 */

static	unsint	p,pe;		/* バッファー内ポインター */
static	unsint	MLEN;		/* 入力最大長さ */
static	unsint	pmode;		/* 動作モード */
static	unsint	fins;		/* インサートモードフラグ  */
static	int	stx,sty;	/* 表示開始座標 */
static	int	bx;		/* 前行ｘ座標 */
static	int	yy;		/* 行ｙ相対座標 */
static	unsint	*buff;		/* 内部バッファー */
static	unchar	*enter;		/* 入力可能文字群 */
static	int	(*getchss)();	/* キー入力関数 */

/*****************************************************************************/
/* チェック */
/*****************************************************************************/

static unsint CheckSPC()
/* check point space/tab */
{
unsint c;
	c=buff[p];
	return(c==SPC||c==TAB);
}

static short BLen(unsint c)
/* バッファー内バイト数＋この文字のサイズ */
{
unsint i;
short len;
	len=pe+1+(c>=256); /* 基本長さ＋この文字のサイズ */
	for (i=0;i<pe;i++) {
		if (buff[i]>=256) len++;
	}
	return(len);
}

#define BCheckSize(pp)	CheckSize(buff[pp])

static unsint CheckSize(unsint c)
/* 文字表示幅 */
/* 半角、１／２角、１／４角文字＝１ */
/* 全角文字＝２ */
{
	return(2-((c<256 && c>=' ')||is24(c>>8)));
}

static short BKlen2(unsint ps)
/* ポイント以降バッファー内容の表示長さを計算する */
{
unsint i;
short len=0;
	for (i=ps;i<pe;i++) len+=BCheckSize(i);
	return(len);
}

/*****************************************************************************/
/* カーソル移動 */
/*****************************************************************************/

#define	PHome()	p=0	/* point home */
#define PEnd()	p=pe	/* point end */

static unsint HomeSub()
/* 行頭へ */
{
unsint pb;
	pb=p;
	PHome();
	B_LOCATE(stx,sty+yy);
	return(pb);
}

static unsint PRight()
/* point right */
{
	if (p==pe) return(1); /* 移動不可：ポイントが行末にある */
	p++;
	return(0);
}

static unsint PLeft()
/* point left */
{
	if (p==0) return(1); /* 移動不可：ポイントが行頭にある */
	p--;
	return(0);
}

static void CsrRight(unsint pp)
/* pp=移動前のポイント */
{
unsint l;
	l=BCheckSize(pp);
	curright();
	if (l==2) curright();
}

static void CsrLeft(unsint pp)
/* pp=移動後のポイント */
{
	curleft();
	if (BCheckSize(pp)==2) curleft();
}

static void MoveCursor(unsint mode,unsint pb)
/* cursor move : 動けることを確認してから呼び出すこと */
{
short i;
	if (mode==1) { /* front move : home , before word */
		for (i=pb-1;i>=p;i--) CsrLeft(i);
	} else { /* end move : end , next word */
		for (i=pb;i<p;i++) CsrRight(i);
	}
}

static unsint EndSub()
/* 行末へ */
{
unsint pb;
	pb=p;
	PEnd();
	MoveCursor(0,pb);
	return(pb);
}

static unsint SkipSPWord(unsint dir,unsint mode)
/* skip word/space */
/* dir =0:left , 1:right */
/* mode=0:word , 1=space */
{
	while (CheckSPC()==mode) {
		if (dir==0? PLeft():PRight()) return(1); /* 移動不可：行末、行頭にいる */
	}
	return(0);
}

static unsint BeforeWord()
/* point move to before word */
{
unsint i=0;
	if (p==pe) i++; /* 今行末にいる時はワードスキップしない */
	for (;i<3;i++) {
		/* (skip word 1 ->) skip space -> skip word 2 */
		/* i%2 =  0     ->     1       ->    0        */
		if (SkipSPWord(0,i%2)) return(1);
		/* スキップしている間に行頭にきてしまった */
	}
	/* skipした後は単語の１文字前にポイントがあるから */
	p++;
	return(0);
}

static unsint AfterWord()
/* point move to after word */
{
unsint i;
	for (i=0;i<2;i++) {
		/* skip word 1 -> skip space */
		/* i%2 =  0    ->     1      */
		if (SkipSPWord(1,i%2)) return(1);
		/* スキップしている間に行末にきてしまった */
	}
	return(0);
}

/*****************************************************************************/
/* 初期化 */
/*****************************************************************************/

#define Init()		InsSet()

static unsint OpenBuffer(unchar **line0)
/* 先行入力文字列を内部バッファーに展開する */
/* 多行展開のため、改行文字でも終了する */
/* klen()文字数を返す */
{
unsint c,len,pe; /* ローカルpeを使う */
unchar *line;
	line=*line0;
	len=pe=0;
	while (c=*line++,c!=0 && c!='\n') {
		if (chkknj1(c)) c=(c<<8)|*line++;
		len+=(1+c>=256);
		if (len>MLEN) {
			while (c!=0 && c!='\n') c=*line++;
			break;
		}
		buff[pe++]=c;
	}
	if (c==0) line--; /* 0x00の位置 */
	*line0=line;
	return(pe);
}

static unsint CloseBuffer(unchar **line0)
/* 内部バッファーを文字列にする */
{
unsint c,i=0,j=0;
unchar *line;
	line=*line0;
	while (i<pe) {
		c=buff[i++];
		if (c>=256) line[j++]=(c>>8);
		line[j++]=(unchar)(c);
	}
	*line0=&line[j];
	return(j); /* 文字数を返す */
}

static void InsSet()
/* insert mode set from pmode:BIT7 */
{
	fins=(pmode&BIT7? 1:0); /* insert/overwrite mode */
	LEDMOD(4,fins); /* insert LED on/off */
}

static unchar pkeys[712];	/* 全再定義可能キー待避エリア */

static void KeySet()
/* 再定義可能キー待避＆セット */
{
	/* 全キー定義内容待避 */
	FNCKEYGT(0,pkeys);
	
	/* キー定義 */
	keysetc(K_INS,  0x09);	/* INS   */
	keysetc(K_DEL,  0x07);	/* DEL   */
	keysetc(K_RIGHT,0x1c);	/* RIGHT */
	keysetc(K_LEFT, 0x1d); 	/* LEFT  */
	keysetc(K_CLR,  0x0c);	/* CLR   */
	keysetc(K_HOME, 0x16);	/* HOME  */
	keysetc(K_UP,   0x1e);	/* UP    */
	keysetc(K_DOWN, 0x1f);	/* DOWN  */
}

#define	KeyReset()	FNCKEYST(0,pkeys)	/* 全再定義可能キー復帰 */

/*****************************************************************************/
/* 挿入 */
/*****************************************************************************/

static unsint Ins(unsint mode)
/* １文字挿入 */
/* mode=0:空白挿入、1:ポイント文字挿入 */
{
unsint i,ppe;
	if (p==pe) return(1); /* 行末にいる：挿入する必要無し */
	ppe=pe;
	while (BLen(mode==0? ' ':buff[p])>MLEN) {
		if (pmode&BIT0) { /* 全スクリーンモード */
			if (buff[pe-1]==' ') { /* 行末がスペースの時 */
				/* スペースを削除して挿入を試みる */
				pe--;
				continue;
			}
		}
		pe=ppe;
		return(2); /* line over */
	}
	for (i=pe-1;i>=p;i--) buff[i+1]=buff[i];
	if (mode==0) buff[p]=' '; /* 空白が挿入される */
	/* そうでない時はポイントにあった文字がそのまま残る */
	pe++;
	return(0);
}

/*****************************************************************************/
/* 表示 */
/*****************************************************************************/

static void CPrint(unsint c)
/* １文字表示＆バッファー書き込み */
{
	B_PUTC(c);
	buff[p++]=c; /* ポインターも進める */
}

static void LinePrint(unsint ps,short olen)
/* ポイント以降表示 */
{
short i;
	/* とりあえず表示できる分表示する */
	for (i=ps;i<pe;i++) {
		B_PUTC(buff[i]);
		olen-=BCheckSize(i); /* 前表示した分と今表示した分の差 */
	}
	/* 前の表示が今表示したものより長かった時、長い部分＝上書きされない部分を消す */
	if (olen>0) { /* olen<0なら消さない */
	    unchar line[96*2+1];
	    int x,y;
		string(line,' ',(int)olen);
		cursor(&x,&y);
		B_PRINT(line);
		B_LOCATE(x,y);
	}
	/* カーソルをポイントに戻す */
	MoveCursor(1,pe); /* pe -> p */
}

/*****************************************************************************/
/* 削除 */
/*****************************************************************************/

static unsint Del()
/* １文字削除 */
{
unsint i;
	if (p==pe) return(1); /* 削除不可：行末にいる */
	for (i=p+1;i<=pe;i++) buff[i-1]=buff[i];
	pe--;
	return(0);
}

static void DelSub()
{
short len;
	len=BKlen2(p);
	if (!Del()) LinePrint(p,len);
}

static unsint DelBefore()
/* ポイント前全削除 */
{
unsint i;
	if (p==0) return(1); /* 削除不可：行頭にいる */
	for (i=p;i<=pe;i++) buff[i-p]=buff[i];
	pe-=p;
	PHome();
	return(0);
}

static unsint DelAfter()
/* ポイント後全削除 */
{
	if (p==pe) return(1); /* 削除不可：行末にいる */
	/* ポイントをpeにするだけ */
	pe=p;
	return(0);
}

static unsint DelWord()
/* 単語クリア */
{
unsint mode;
	if (p==pe) return(1); /* 削除不可：行末にいる */
	/* 最初の１文字で削除モードを決定する */
	mode=CheckSPC();
	/* on space = delete space , on word  = delete word  */
	while (CheckSPC()==mode) if (Del()) break;
	return(0);
}

static unsint DelLine()
/* １行クリア */
{
	if (pe==0) return(1); /* まだ１文字も入力されていない */
	p=pe=0;
	return(0);
}

static void DelAllAfter(int y,int wx,int wy)
/* 指定行以降全削除 */
{
unsint point;
	for (;y<wy;y++) {
		pe=*buff++;
		point=BKlen2(0);
		pe=*(buff-1)=0; /* pe=0 */
		B_LOCATE(stx,sty+y);
		LinePrint(0,point);
		buff+=(wx+1);
	}
	kflush();
}

/*****************************************************************************/
/* ストア */
/*****************************************************************************/

static unsint WriteChar(unsint c)
/* 文字書き込み */
/* return : 0=ok , 1=can't enter code , 2=line over */
{
unsint cb;
short len;
	if (*enter) { /* 入力文字制限があるとき */
	   int ret;
		ret=kstrchar(enter,c);
		if (pmode&BIT6) { /* 入力不可文字 */
			if (ret>=0) return(1); /* 入っていたら入力不可 */
		} else { /* 入力可能文字 */
			if (ret==-1) return(1);/* 入っていなかったら入力不可 */
		}
	}
	
	/* １文字書き込み */
	if (p==pe) { /* 新規書き込み */
		if (BLen(c)>MLEN) return(2); /* １行が一杯になる時 */
		pe++; /* エンドポインターも進める */
		CPrint(c); /* 表示 */
		return(0);
	}
	
	/* 上書き */
	if (fins) { /* insert mode */
		len=CheckSize(c);
		while (len--) { /* 表示幅分 */
			if (Ins(0)) {
				beep();
				InsSet();
				break;
				/* ret=2:insert disable(ret=1:p=peはなし) */
			}
		}
	}
	
	/* 前の文字と今の文字の表示長さの差を求める */
	cb=buff[p]; /* 書込む前の文字 */
	len=CheckSize(cb)-CheckSize(c);
	if (!fins && len==0) { /* 差がない時はそのまま */
		CPrint(c);
		return(0);
	}
	
	if (len>=0) { /* 表示長さが短くなる時 */
		/* ポイント以降再表示のみ */
		CPrint(c); /* 表示 */
		LinePrint(p,BKlen2(p)+len); /*+lenが重要(後の残り文字を消す)*/
		return(0);
	}
	
	/* 表示長さが長くなる時 */
	if (p+1==pe) { /* となりが行末の時 */
		if (BLen(c)-1-(cb>=256)>MLEN) return(2); /* １行が一杯になる */
		CPrint(c); /* 表示（上書きするほうが長いのでこれだけでいい）*/
		return(0);
	}
	/* 行真ん中での上書き */
	/* となりの１文字もつぶして入れる */
	/* 今は文字の長さが１／２しかないのでこれでいい */
	len=BKlen2(p);
	p++; Del(); p--;
	CPrint(c); /* 表示 */
	LinePrint(p,len);
	return(0);
}

/*****************************************************************************/
/* １行処理 */
/*****************************************************************************/

static int LineEdit(int kexit)
/* buff,p,pe,MLEN,enterで１行処理をする */
/* pは全行共通 */
{
unsint c,knj1=0,pp;
	while (c=getchss(0,kexit)
		,strchr("\x1b\x03\x0d\x1e\x1f\x1a\x0c\x16\x17\x19",c)==NULL
		&& c!=kexit) {
		
		if (c>=' ') { /* 通常文字 */
			if (knj1==0) { /* 漢字１バイト目? */
				if (chkknj1(c)) { /* yes */
					knj1=c;
					continue;
				}
			}
			/* １文字内部処理＆表示 */
			/* 1/2,1/4角文字入力不可の時 *追加*/
			if (!(pmode&BIT8) && is24(knj1)) beep();
			else {
				if (knj1) c=(knj1<<8)|c; /* 漢字完成 */
				if (WriteChar(c)) beep();
			}
			knj1=0;
			continue;
		}
		
		/* コントロールコード */
		knj1=0; /* 漢字モード解除 */
		switch (c) {
			/*** 移動系コマンド ***/
			case 0x11: /* home of line : ^Q */
				InsSet();
				HomeSub();
				continue;
			
			case 0x10: /* end of line : ^P */
				InsSet();
				EndSub();
				continue;
			
			case 0x1c: /* cursor right */
				InsSet();
				pp=p;
				if (!PRight()) CsrRight(pp);
				else { /* p=pe */
				    if (pmode&BIT0) {
					if (BLen(' ')<=MLEN) {
						CPrint(' ');
						pe++;
						continue;
					}
				    }
				    if (pmode&BIT4) return(0x0d);
				}
				continue;
			
			case 0x1d: /* cursor left */
				InsSet();
				if (!PLeft()) CsrLeft(p);
				else {
				    if (pmode&BIT4) return(0x0a);
				}
				continue;
			
			case 0x06: /* after word : ^F */
				InsSet();
				pp=p;
				AfterWord();
				MoveCursor(0,pp);
				continue;
			
			case 0x01: /* before word : ^A */
				InsSet();
				pp=p;
				BeforeWord();
				MoveCursor(1,pp);
				continue;
			
			/*** 削除系コマンド ***/
			case 0x08: /* back space : ^H/BS */
				if (PLeft()) continue; /* 行頭にいる時 */
				CsrLeft(p);
			case 0x07: /* Delete : ^G/DEL */
				DelSub();
				kflush(); /* キーリピートしないように */
				continue;
			
			case 0x19: /* clear line : ^Y */
			case 0x15: /* Delete home to cursor : ^U */
				InsSet();
				if (pe>0) { /* 入力文字があるときのみ */
				    pp=BKlen2(0);
				    if (c==0x15) DelBefore(); else DelLine();
				    B_LOCATE(stx,sty+yy);
				    LinePrint(0,pp);
				    kflush();
				}
				continue;
			
			case 0x14: /* Delete word : ^T */
				pp=BKlen2(p);
				if (!DelWord()) LinePrint(p,pp);
				continue;
			
			case 0x0b: /* erase line from cursor to end : ^K */
				InsSet();
				pp=BKlen2(p);
				if (!DelAfter()) LinePrint(p,pp);
				continue;
			
			/*** 挿入系コマンド ***/
			case 0x0f: /* insert : ^O */
			case 0x18: /* insert type2 : ^X */
				InsSet();
				pp=BKlen2(p);
				switch (Ins(c==0x18)) {
				    case 0: /* ok */
					LinePrint(p,pp);
					break;
				    case 2: /* line overflow */
					beep();
					kflush();
				}
				continue;
			
			case 0x09: /* insert mode : TAB/INS */
				fins^=1;
				LEDMOD(4,fins);
				continue;
		}
	}
	/* return with key code */
	return(c);
}

/*****************************************************************************/
/* メイン */
/*****************************************************************************/

GetMSEStatus(int *cp,int *cy,int *xx)
/* mini_screen_edit()の内部情報を取得する */
/* 継続処理のために利用する（外部インターフェース）*/
{
	*cp=(int)p;
	*cy=(int)yy;
	*xx=bx-stx;
}

SetMSEStatus(int cp,int cy,int xx)
/* mini_screen_edit()の内部情報を与える */
/* 継続処理のために利用する（外部インターフェース）*/
{
	p=cp;
	yy=cy;
	bx=xx+stx;
}

/*****************************************************************************/

int mini_screen_edit(lines,wx,wy,mode,plines,enters,kexit,getchs)
unchar *lines,*plines,*enters;
int wx,wy,mode,kexit,(*getchs)();
/*	lines : 入力バッファー
		\n毎に行分割する。
	plines: 先行入力文字列
	   	もし、文字列がwxより長かった場合はwx以下になる。
        enters: 入力可能文字：全角文字も指定可
      mode
	bit 0 : 全スクリーン入力 0=off , 1=on
		実際にはカーソル移動がp>=peでスペース入力になるだけ
	bit 1 : SetMSEStatus()の内部情報による処理の継続 0=off , 1=on
	bit 3 : カーソル上下のロール 0=STOP , 1=ROLL
	bit 4 : カーソル左右のロール 0=STOP , 1=ROLL
	bit 6 : enter制御 0=入力可能 , 1=入力不可
	bit 7 : insert mode 0=off , 1=on
	bit 8 : 1/2,1/4角文字が使えるかどうか 0=not , 1=ok
*/
{
unsint *buff0,point;
int ret,i,xx;
	/* 内部バッファー確保（全行分）*/
	/* +1:pe , +1:EOS */
	buff0=(unsint *)newmalloc((1+wx+1)*sizeof(unsint)*wy);
	if (buff0==NULL) return(-1); /* error */
	
	/* 初期設定 */
	KeySet();
	cursor(&stx,&sty);
	if (wx==0) stx=0; /* 幅指定なし */
	Init();
	enter=enters;
	pmode=mode;
	if (pmode&BIT0) pmode&=~BIT8; /* BIT0=1のときは1/2,1/4角文字は使用不可 */
	MLEN=wx;
	getchss=getchs;
	
	/* 全行pe初期化 */
	buff=buff0;
	for (i=0;i<wy;i++) {
		*buff=0;
		buff+=(wx+2); /* next line */
	}
	
	/* 先行入力：入力文字制限処理なし */
	i=0;
	buff=buff0;
	while (*plines!=0) {
		*buff++; /* buff[0]=peだから */
		pe=OpenBuffer(&plines);
		*(buff-1)=pe; /* 行毎のpeを記録 */
		B_LOCATE(stx,sty+i);
		if (pe>0) LinePrint(0,0); /* 行表示 */
		buff+=(wx+1); /* next line */
		if (++i>=wy) break; /* 行が多すぎるときはカットする */
	}
	
	/* カーソルをホームに戻す */
	if (!(pmode&BIT1)) bx=yy=p=0;
	buff=buff0+(wx+2)*yy;
	
	/* メイン処理：基本的に行毎に処理は分かれる */
	do {
		/* １行内部処理 */
		pe=*buff++;
		
		/* ポイント移動 */
		xx=stx;
		if (p>0) {
			i=0;
			if (pmode&BIT0) {
				while (i<=MLEN && xx<bx) {
					if (i>=pe) buff[i]=' ';
					xx+=CheckSize(buff[i++]);
				}
				if (xx>bx) xx-=CheckSize(buff[--i]);
				if (i>=pe) *(buff-1)=pe=i;
			} else {
				while (i<pe && xx<bx) xx+=CheckSize(buff[i++]);
				if (xx>bx) xx-=CheckSize(buff[--i]);
			}
			p=i;
		}
		B_LOCATE(xx,sty+yy);
		ret=LineEdit(kexit);
		*(buff-1)=pe;
		bx=pos();
		
		/* 行またぎ処理 */
		switch (ret) {
			default:continue;
			
			case 0x19: /* clear line : ^Y */
				buff--;
				for (i=yy;i<wy;i++) {
					B_LOCATE(stx,sty+i);
					pe=*buff++;
					point=BKlen2(0);
					/* 下の行を上に詰める */
					buff--;
					if (i==wy-1) *buff=0; /* pe=0:最終行 */
					else memcpy(buff,buff+(wx+2),sizeof(unsint)*(wx+2));
					pe=*buff++;
					LinePrint(0,point);
					buff+=(wx+1);
				}
				kflush();
				break;
			
			case 0x0a:/* cursor up & cursor line end */
			case 0x1e:/* cursor up */
				if (yy==0) {
					if (pmode&BIT3) yy=wy-1;
					else {
						beep();
						kflush();
						break;
					}
				} else yy--;
				break;
			
			case 0x0d:/* [RET] */
				p=0;
			case 0x1f:/* cursor down */
				if (yy==wy-1) {
					if (pmode&BIT3) yy=0;
					else {beep(); kflush();}
				} else yy++;
				break;
			
			case 0x17:/* END */
				yy=wy-1;
				break;
			
			case 0x0c:/* CLR */
				buff=buff0;
				DelAllAfter(0,wx,wy);
			case 0x16:/* HOME */
				p=yy=0;
				break;
			
			case 0x1a:/* ^Z : カーソル位置から最終行末までクリア */
				/* 今いる行は行末までクリア */
				point=BKlen2(p);
				if (!DelAfter()) LinePrint(p,point);
				*(buff-1)=pe;
				/* 次の行からは全削除 */
				buff+=(wx+1);
				DelAllAfter(yy+1,wx,wy);
				InsSet();
				continue;
		}
		buff=buff0+(wx+2)*yy;
		pe=*buff;
		if (!(pmode&BIT0)) {
			if (p>pe||ret==0x0a||ret==0x17) {
				p=pe; /* cursor line end */
				bx=128; /* ポイント設定機能をだますため */
			}
		}
		InsSet(); /* 行をまたぐときはｲﾝｻｰﾄﾓｰﾄﾞを初期化する */
	} while (ret!=BREAK && ret!=ESC && ret!=kexit);
	
	/* 内部バッファーを文字列に換える */
	buff=buff0;
	for (i=0;i<wy;i++) {
		pe=*buff++;
		CloseBuffer(&lines);
		*lines++='\n';
		buff+=(wx+1); /* next line */
	}
	*(lines-1)=0;
	
	free(buff0);
	KeyReset();
	LEDMOD(4,0);
	return(ret);
}

/*****************************************************************************/
#if 0
main()
{
#define LWD	80	/* １行幅 */
#define LN	10	/* 行数 */
int ret,cp,cy,xx;
char line[(LWD+1)*LN]; /* +1 for EOS */
	cls();
	B_LOCATE(0,LN);
	for (ret=0;ret<LWD;ret++) B_PUTC('-');
	B_LOCATE(0,0);
	ret=mini_screen_edit(line,LWD,LN,BIT0,"先行\n入力です\n\nOk?","",0,getch3);
	GetMSEStatus(&cp,&cy,&xx);
	B_LOCATE(0,11);
	printf("p=%d,yy=%d,bx=%d,ret=%02x\n%s\nHit Any Key...",cp,cy,xx,ret,line);
	getch();
	
	cls();
	B_LOCATE(0,LN);
	for (ret=0;ret<LWD;ret++) B_PUTC('-');
	B_LOCATE(0,0);
	SetMSEStatus(cp,cy,xx);
	ret=mini_screen_edit(line,LWD,LN,0,line,"",0,getch3);
	B_LOCATE(0,11);
	printf("ret=%02x\n%s\n",ret,line);
}
#endif
/*****************************************************************************/
