/*

MercuryInstaller テキストビュワールーチン

*/

#include<stdio.h>
#include<stdlib.h>
#include<jctype.h>
#include<string.h>
#include<jstring.h>
#include<farstr.h>
#include<dos.h>
#include<stdarg.h>

#include<LHACCESS.H>
#include"mercury.h"
/*===========================================================================*/
/*                                 定数と変数                                */
/*===========================================================================*/
#define	TEXT_STARTYPOS	3	/* テキスト表示開始位置 */
#define	TEXT_YWIDTH	(CON_YWIDTH-TEXT_STARTYPOS)
			/* テキスト表示行数(putmessage専用行は除いてある) */

static	char	far	**Text;		/* テキストバッファ             */
static	int		Linenum;	/* テキストの総行数             */
static	int		Linepos;	/* 現在表示中の内容の開始行番号 */
static	int		Bufsize;	/* バッファの行数               */
/*===========================================================================*/
/*                            ファイル読み込み関連                           */
/*===========================================================================*/

/*------------------------------メモリの確保---------------------------------*/
/* まず指定容量のメモリの確保を試み、失敗した場合は少しずつ容量を減らしてゆく*/
/*---------------------------------------------------------------------------*/
static	void	*nearcalloc_down(size_t num,size_t width,size_t *ret)
{
	void	*p;

	while	((p=calloc(num,width))==NULL && num>1)
		num--;

	if	(p==NULL)
		*ret = 0;
	else
		*ret = num;

	return p;
}

static void far *maxfarmalloc(long *size)
{
	long		s = farcoreleft();
	void	far	*p;

	do
	{
		p = farmalloc(s);
		if	(p!=NULL)
		{
			*size = s;
			return p;
		}
	} while(--s);

	return NULL;
}

/*-----------------------------仮想ファイルの処理----------------------------*/
/* LHACCESSライブラリを経由した書庫ファイルか、それとも単なるベタテキストかを*/
/* 全く意識せずに使用するためのルーチン                                      */
/*---------------------------------------------------------------------------*/
static	struct
{
	enum	{NONE,LZH,TEXT}	mode;
	union
	{
		FILE	*fp;
		ROOT	root;
	} pointer;
} File;

static	int	File_ungetc;

static	void	file_open(char *filename)
{
	char	*p = jstrchr(filename,'|');

	File_ungetc = EOF;

	if	(p!=NULL)	/* 書庫ファイルを通したアクセス */
	{
		*p = '\0';

		if	(islzh(filename)!=0 || 
				open_root(filename,&File.pointer.root)!=0)
		{
			*p = '|';
			goto error;
		}

		*p = '|';

		if	(get_all_header(&File.pointer.root)!=0 || 
			 lzh_fopen2(&File.pointer.root,p+1)!=0 )
		{
			close_root(&File.pointer.root);
			goto error;
		}

		File.mode = LZH;
		return;
	}
	else
	{
		if	((File.pointer.fp=fopen(filename,"r"))==NULL)
			goto error;

		File.mode = TEXT;
		return;
	}
error:
	File.mode = NONE;
	return;
}

static	void	file_close(void)
{
	switch	(File.mode)
	{
	case LZH:
		close_root(&File.pointer.root);
		break;
	case TEXT:
		fclose(File.pointer.fp);
		break;
	}
}

static	int	file_getc(void)
{
	int		c;

	if	(File_ungetc!=EOF)
	{
		c = File_ungetc;
		File_ungetc = EOF;
		return c;
	}
	switch	(File.mode)
	{
	case NONE:
		return EOF;

	case LZH:
		do
			c = lzh_fgetc();
		while	(c=='\r' || c=='\x1a');

		return c;

	case TEXT:
		return fgetc(File.pointer.fp);
	}
}

#define	file_ungetc(c) (File_ungetc=(c))
/*---------------------------テキストの読み込み------------------------------*/
static	bool	readfile(char *filename)
{
	char far *p;
	int	xlen=0;
	int	c;
	int	isknj = 0;
	long	bufsize,buf_left;

	file_open(filename);

	Linenum = 0;

	Text = nearcalloc_down(4096,sizeof(char far *),&Bufsize);
	if	(Text==NULL)
	{
		putmessage("メモリ不足です");
		return 0;
	}

	p = Text[0] = maxfarmalloc(&bufsize);
	buf_left = bufsize;

	while	((c=file_getc())!=EOF)
	{
		*p++ = c;
		buf_left--;

		if	(c=='\t')
			xlen += CON_TABSIZE - xlen%CON_TABSIZE;
		else
			xlen++;

		if	(!isknj && iskanji(c))
			isknj = 1;
		else
			isknj = 0;

		if	(xlen>=CON_XWIDTH || c=='\n')
		{
			if	(isknj)
			{
				file_ungetc(c);
				p--;
				buf_left++;
				isknj = 0;
			}

			*p = '\0';
			xlen = 0;

			if	(++Linenum == Bufsize)
			{
				putmessage("行数が多すぎるので途中で打ち切ります");
				break;
			}

			p += 1L;
			Text[Linenum] = p;
			buf_left--;
		}

		if	(buf_left<2)
		{
			putmessage("メモリ不足です 途中で打ち切ります;bufsize=%ld,buf_left=%ld",bufsize,buf_left);
			break;
		}
	}

	if	(xlen)
	{
		*p = '\0';
		Linenum++;
	}

	if	(Linenum==0)
	{
		farfree(Text[0]);
		free(Text);
	}
	else
	{
		farrealloc(Text[0],bufsize-buf_left);
		realloc(Text,Linenum*sizeof(Text[0]));
	}
	file_close();
	return 1;
}
/*--------------------------ファイルバッファの解放---------------------------*/
static	void	memory_release(void)
{
	if	(Linenum>0)
	{
		farfree(Text[0]);
		free(Text);
	}
}
/*-----------------------------パス名の生成----------------------------------*/
static	void	mkpath(char *buf,char far *dir,char far *filename)
{
	if	(dir==NULL || filename[1]==':')
		buf[0] = '\0';
	else
	{
		buf[0] = Drive;
		buf[1] = ':';
		if	(filename[0]!='\\')
		{
			if	(dir!=NULL)
				far_strcpy(buf+2,dir);
			else
				buf[2] = '\0';

			strcat(buf,"\\");
		}
		else
			buf[2] = '\0';
	}

	far_strcat(buf,filename);
}
/*-----------------------------ファイルの検索--------------------------------*/
/* ○指定されたディレクトリからファイルを検索し、最初に見つかったファイルの  */
/* 　ファイル名をbufに返す。                                                 */
/* ○ファイルが見つからない場合は、bufに空文字列をセットする。               */
/* ○なお、dirはDataから引っ張ってくる関係でfarポインタになっている。        */
/*---------------------------------------------------------------------------*/
static	bool	findfile(char far *dir,char *wildcard,char buf[])
{
	char		*p;
	struct	find_t	fib;

	mkpath(buf,dir,wildcard);

	if	((p=jstrrchr(buf,'\\'))!=NULL)
		p++;
	else if	(buf[1]==':')
		p = buf+2;
	else
		p = buf;

	if	(_dos_findfirst(buf,_A_NORMAL,&fib)==0)
	{
		strcpy(p,fib.name);
		return 1;
	}
	else
	{
		buf[0] = '\0';
		return 0;
	}
}

/*===========================================================================*/
/*                     画面表示関連(1):テキストのスクロール                  */
/*===========================================================================*/

/*----------------------------------行を表示---------------------------------*/
static	void	showoneline(int ypos)
{
	printf("\033[%d;1f\033[2K",ypos+TEXT_STARTYPOS);

	if	(Linepos+ypos>=Linenum)
		return;

	far_fputs(Text[Linepos+ypos],stdout);
}
/*---------------------画面全体の表示(インライン関数)------------------------*/
#define	showtext() do				\
{						\
	int	i;				\
						\
	for	(i=0 ; i<TEXT_YWIDTH ; i++)	\
		showoneline(i);			\
} while(0)
/*-------------------------------画面の初期化--------------------------------*/
static	void	screen_init(char far *title,char *filename)
{
	static	char far *title_save;
	static	char	*filename_save;
	char	buf[TITLEWIDTH+1];

	if	(title==NULL)
		title = title_save;
	else
		title_save = title;

	if	(filename==NULL)
		filename = filename_save;
	else
		filename_save = filename;

	far_strcpy(buf,title);
	printf("\033[2J\033[7;36m%-*s\033[37;7m%-*s\n",TITLEWIDTH,buf,
					CON_XWIDTH-TITLEWIDTH,"    [F5]HELP");
	printf("\033[2;1f\033[7;36m%-*s\033[0m",CON_XWIDTH,filename);

	showtext();
}
/*-----------------------------画面のスクロール------------------------------*/
static	void	scrolldown(void)
{
	if	(Linepos==Linenum-1)
		return;

	Linepos++;

	printf("\033[%d;1f",TEXT_STARTYPOS);

	if	(Flag_isfmesc)	printf("\033R");
	else			printf("\033[M");

	showoneline(TEXT_YWIDTH-1);
}

static	void	scrollup(void)
{
	if	(Linepos==0)
		return;

	Linepos--;

	printf("\033[%d;1f",TEXT_STARTYPOS);

	if	(Flag_isfmesc)	printf("\033E");
	else			printf("\033[L");

	printf("\033[%d;1f\033[2K",TEXT_STARTYPOS+TEXT_YWIDTH);

	showoneline(0);
}

/*===========================================================================*/
/*               画面表示関連(2):ウィンドウシステムライブラリ                */
/*===========================================================================*/
/* これらの関数はtextviewer()の下でしか使えないので要注意。                  */
/*===========================================================================*/

static	struct
{
	int	xstart,ystart;	/* 文字表示開始xy座標 */
	int	xsize,ysize;	/* ウィンドウのサイズ */
} Window;

/*-----------------------------ウィンドウを開く------------------------------*/
extern	void	openwindow(int ysize,int xsize)
{
	int	i;

	Window.ysize = ysize;
	Window.xsize = xsize;

	Window.xstart = (CON_XWIDTH-xsize)/2;
	Window.ystart = (CON_YWIDTH-ysize)/2;

	printf("\033[%d;%df\033[36m+",Window.ystart-1,Window.xstart-1);
	for	(i=0 ; i<Window.xsize ; i++)
		putchar('-');

	printf("+\033[%d;%df+",Window.ystart+Window.ysize,Window.xstart-1);
	for	(i=0 ; i<Window.xsize ; i++)
		putchar('-');

	putchar('+');

	for	(i=0 ; i<Window.ysize ; i++)
	{
		printf("\033[%d;%df|%*s|",Window.ystart+i,Window.xstart-1,
							Window.xsize,"");
	}

	printf("\033[0m");
}
/*----------------------------ウィンドウを閉じる-----------------------------*/
extern	void	closewindow(void)
{
	int	i;

	for	(i=-1 ; i<=Window.ysize ; i++)
		showoneline(Window.ystart+i-TEXT_STARTYPOS);
}
/*--------------------------------文字列の出力-------------------------------*/
/* ウィンドウの各行の最後の1バイトは全角文字の禁則処理専用に予約済。         */
/*---------------------------------------------------------------------------*/
extern	void	window_putstr(int ypos,char *format,...)
{
	va_list	ap;
	char	buf[256];
	char	*p = buf;
	char	c;
	int	xpos = 0;

	va_start(ap,format);
	vsprintf(buf,format,ap);

	while	((c=*p++)!='\0')
	{
		if	(xpos==0)
			printf("\033[%d;%df%*s\033[%d;%df",
					Window.ystart+ypos,Window.xstart,
					Window.xsize,"",
					Window.ystart+ypos,Window.xstart);

		putchar(c);
		xpos++;

		if	(iskanji(c))
		{
			putchar(*p++);
			xpos++;
		}

		if	(xpos>=Window.xsize-1 || c=='\n')
		{
			ypos++;
			xpos = 0;
		}
	}
}
/*-----------------------------文字列を入力する------------------------------*/
/* 0＜strlen(message)≦len≦CON_XWIDTH-5。ただしチェックはしない             */
/*---------------------------------------------------------------------------*/
extern	int	window_strinput(char *message,char *buf,unsigned len)
{
	int	f;

	openwindow(2,len+3);

	window_putstr(0,"%s",message);
	window_putstr(1,"[%*s]",len,"");

	showcursor(1);
	printf("\033[%d;%df",Window.ystart+1,Window.xstart+1);
	f = ds_strinput(buf,len);
	showcursor(0);

	closewindow();
	return f;
}
/*---------------------選択肢を提示して入力を求める--------------------------*/
/* messageは最大で64バイト×3行におさまること。ただしチェックはしない        */
/*---------------------------------------------------------------------------*/
/* messageの後には、16個以下のconst char *が続く。たとえば"Abort"や"I:中止"の*/
/* ように、先頭の1バイトは入力する際の文字ともなる。なお1行に収まるようにする*/
/* こと                                                                      */
/*---------------------------------------------------------------------------*/
extern	int	window_select(char *message,...)
{
	char	letter[16];
	char	buf[256];
	char	*p;
	int	n=0;
	va_list	ap;

	va_start(ap,message);

	openwindow(4,64);
	window_putstr(0,"%s",message);

	buf[0] = '\0';

	while	((p=va_arg(ap,char *))!=NULL)
	{
		strcat(buf,p);
		strcat(buf,"     ");
		letter[n++] = toupper(*p);
	}

	va_end(ap);
	window_putstr(3,"%s",buf);

	while	(1)
	{
		int	c = ds_getch();
		int	i;

		if	(c>=0x100)
			continue;

		c = toupper(c);

		for	(i=0 ; i<n ; i++)
		{
			if	(letter[i]==c)
			{
				closewindow();
				return c;
			}
		}
	}
}

/*===========================================================================*/
/*                               各種コマンド                                */
/*===========================================================================*/

/*-------------------印刷(厳密には他のファイルへの出力)----------------------*/
static	void	printout(void)
{
	static	char	filename[128] = "\\dev\\prn";
	char	buf[128];
	FILE	*fp;
	int	i;

	strcpy(buf,filename);
	if	(!window_strinput("印刷します 出力先デバイスを指定してください",buf,64))
		return;

	strcpy(filename,buf);

	openwindow(2,64);

	fp = fopen(filename,"w");
	if	(fp==NULL)
	{
		window_putstr(0,"%sがオープンできません",filename);
		closewindow();
		return;
	}

	for	(i=0 ; i<Linenum ; i++)
	{
		if	(i%64==0)
			window_putstr(0,"出力中 %d/%d",i,Linenum);

			/* 中止の確認はint24Hハンドラがやってくれているはず */
		if	(far_fputs(Text[i],fp))
		{
			window_putstr(0,"中止しました");
			goto error;
		}
	}

	window_putstr(0,"終了しました");
error:
	ds_getch();
	closewindow();
	fclose(fp);
}
/*------------------------------ヘルプの出力---------------------------------*/
static	void	helpmessage(void)
{
	int	i;

	static	char	*message[] =
	{
		"[↑][↓]   スクロール",
		"[F1]       概要ファイル/マニュアル切り替え",
		"[F2]       現在見ているファイルの印刷",
		"[F3]       インストール",
		"[F4]       DOSコマンドライン",
		"[F5]       ヘルプ(この画面)",
		"[ESC][F10] 終了",
	};

	openwindow(MEMBERSOF(message),64);

	for	(i=0 ; i<MEMBERSOF(message) ; i++)
		window_putstr(i,message[i]);

	ds_getch();
	closewindow();
}

/*===========================================================================*/
/*                     テキストビュワーのメインルーチン                      */
/*===========================================================================*/
extern	void	textviewer(struct DATA far *data)
{
	char	filename[2][256];	/* GGG/MANのファイル名 */
	bool	ismanual = 0;

	if	(data->readme!=NULL)
		mkpath(filename[0],data->dir,data->readme);
	else if	(findfile(data->dir,"*.ggg"  ,filename[0]))	;
	else if	(findfile(data->dir,"!*.*"   ,filename[0]))	;
	else if	(findfile(data->dir,"read*.*",filename[0]))	;
	else if	(findfile(data->dir,"*.doc"  ,filename[0]))	;

	if	(data->manual!=NULL)
		mkpath(filename[1],data->dir,data->manual);
	else if	(findfile(data->dir,"*.rrr",filename[1]))	;
	else if	(findfile(data->dir,"*.man",filename[1]))	;
	else if	(findfile(data->dir,"*.doc",filename[1]))	;

again:
	readfile(filename[ismanual]);
	Linepos = 0;
	screen_init(data->title,filename[ismanual]);

	while	(1)
	{
		switch	(ds_getch())
		{
		case FKEY_UP:
			scrollup();
			break;
		case FKEY_DOWN:
			scrolldown();
			break;
		case FKEY_LEFT:
			if	((Linepos-=TEXT_YWIDTH)<0)
				Linepos=0;
			showtext();
			break;
		case FKEY_RIGHT:
			if	((Linepos+=TEXT_YWIDTH)>=Linenum)
				Linepos = Linenum-1;
			showtext();
			break;
		case FKEY_F1:
			ismanual = !ismanual;
			memory_release();
			goto again;
		case FKEY_F2:
			printout();
			break;
		case FKEY_F3:
			installer(data);
			break;
		case FKEY_F4:
			showcursor(1);
			printf("\033[2J");
			system("");
			showcursor(0);
			screen_init(NULL,NULL);
			break;
		case FKEY_F5:
			helpmessage();
			break;
		case FKEY_F10:
		case FKEY_ESC:
			memory_release();
			return;	/* 画面を元に戻すのは親関数の仕事 */
		}
	}
}
/*---------------------------End of textviewer.c-----------------------------*/
