/*
 * Zoom accessory
 *
 */

#include <osbind.h>
#include <linea.h>
#include <aes.h>
#include <vdi.h>

#define elements(p) (p).g_x, (p).g_y, (p).g_w, (p).g_h
#define pointers(p) &(p).g_x, &(p).g_y, &(p).g_w, &(p).g_h

#define clip(lower, val, upper) min((upper), max((lower), (val)))

#define WF_PARTS (NAME+CLOSE+MOVE)

typedef unsigned char	uchar;
typedef unsigned short	ushort;
typedef unsigned long	ulong;

#ifdef LATTICE
extern long _STACK = 2048;
#endif

/*
 * Assembler functions to zoom up a single screen line and to blit the zoomed
 * buffer onto the screen *fast*
 *
 */
extern void zoomLine(void *, void *, short, short);
extern void qblit(void *, void *, short);

#ifdef ACCorPRG
extern int _XMODE;			/* Lattice startup stub variable: 2 = DA */
#endif

short handle;				/* VDI handle */
short m[8];					/* AES message buffer */
short err;					/* Accessory installed? */
uchar zoomed[128][16];		/* Array of zoomed pixels */
struct la_data *data;		/* Line A data */
uchar *display;				/* Pointer to screen memory */
char   *title = "  Zoom ";	/* The DA title as well as the window title */
MFDB  zoomarea = {zoomed, 128, 128, 8, 0, 1, 0, 0, 0};
MFDB  screen;

/*
 * Function:	Convert a window work area so that it lies on a word boundary
 *
 * Parameters:	Original work area, result work area
 *
 * Returns:		None
 *
 */
void
work2work(GRECT *work1, GRECT *work2)
{
	work2->g_x = (work1->g_x + 15) / 16;
	work2->g_x = work2->g_x * 16 - 1;
	work2->g_y = work1->g_y;
	work2->g_w = work1->g_w;
	work2->g_h = work1->g_h;
}

/*
 * Function:	Convert a window border to a word-aligned work area
 *
 * Parameters:	Border area, result work area
 *
 * Returns:		None
 *
 */
void
curr2work(GRECT *curr, GRECT *work)
{
	GRECT temp;

	wind_calc(WC_WORK, WF_PARTS, elements(*curr), pointers(temp));
	work2work(&temp, work);
}

/*
 * Function:	Convert a window border area so that it lies on screen and has
 *				a word-aligned work area
 *
 * Parameters:	Original border area, result border area
 *
 * Returns:		None
 *
 */
void
curr2curr(GRECT *curr1, GRECT *curr2)
{
	GRECT temp;

	wind_get(0, WF_WORKXYWH, pointers(temp));
	curr1->g_x = min(curr1->g_x, temp.g_x + temp.g_w - curr1->g_w - 2);
	curr1->g_y = min(curr1->g_y, temp.g_y + temp.g_h - curr1->g_h - 2);
	curr2work(curr1, &temp);
	wind_calc(WC_BORDER, WF_PARTS, elements(temp), pointers(*curr2));
}

/*
 * Function:	Produce a zoomed display in the global zoomed array (assuming
 *				a THIN_CROSS mouse, the area is offset by the hot-spot of 7,7)
 *
 * Parameters:	Mouse x and y coordinates of top-left corner of area
 *
 * Returns:		None
 *
 */
void
zoom(short x, short y)
{
	uchar *scr;

	x = clip(0, x - 7, data->ld_vwrap * 8 - 16);
	y = clip(0, y - 7, V_Y_MAX - 16);
	scr = display + 2 * (x / 16);
	scr += (long)y * data->ld_vwrap;
	graf_mouse(M_OFF, NULL);
	zoomLine(scr, zoomed, data->ld_vwrap, x % 16);
	graf_mouse(M_ON, NULL);
}

/*
 * Function:	Function called by wind_redraw to redraw the window from the
 *				zoomed buffer.  Also, a white pixel border is drawn on 2 sides
 *
 * Parameters:	Window handle, area to redraw
 *
 * Returns:		1 (continue redrawing)
 *
 */
int
redraw(int win, GRECT *p)
{
	GRECT work;
	short pxy[8];

	pxy[0] = p->g_x; pxy[1] = p->g_y;
	pxy[2] = p->g_x + p->g_w - 1;
	pxy[3] = p->g_y + p->g_h - 1;
	vs_clip(handle, 1, pxy);

	wind_get(win, WF_WORKXYWH, pointers(work));
	pxy[0] = work.g_x + work.g_w - 1;
	pxy[1] = work.g_y;

	pxy[2] = work.g_x;
	pxy[3] = work.g_y;

	pxy[4] = work.g_x;
	pxy[5] = work.g_y + work.g_h - 1;

	vsl_color(handle, WHITE);
	v_pline(handle, 3, pxy);	/* Draw a pixel border on the top & left */
	vs_clip(handle, 0, pxy);

	work.g_x++; work.g_y++;
	work.g_w--; work.g_h--;
	rc_intersect(&work, p);

	pxy[0] = p->g_x - work.g_x; pxy[1] = p->g_y - work.g_y;
	pxy[2] = pxy[0] + p->g_w - 1; pxy[3] = pxy[1] + p->g_h - 1;

	pxy[4] = p->g_x; pxy[5] = p->g_y;
	pxy[6] = p->g_x + p->g_w - 1; pxy[7] = p->g_y + p->g_h - 1;
	vro_cpyfm(handle, S_ONLY, pxy, &zoomarea, &screen);
	return 1;
}

#ifndef LATTICE
/*
 * Function:	Manage window redraws by calling a supplied function for
 *			each valid screen rectangle
 *			From the Lattice C Atari Library Manual pp73-74
 *
 * Parameters:	Window handle, redraw area, redrawing function
 *
 * Returns:		1 if OK, 0 otherwise
 *
 */
int
wind_redraw(int win, GRECT *area, int (*redraw)(int, GRECT *))
{
	GRECT box;
	int   ok = 1;

	graf_mouse(M_OFF, NULL);
	wind_update(BEG_UPDATE);
	wind_get(win, WF_FIRSTXYWH, pointers(box));
	while (box.g_w && box.g_h) {
		if (rc_intersect(area, &box))
			if (!(ok = redraw(win, &box)))
				break;
		wind_get(win, WF_NEXTXYWH, pointers(box));
	}
	wind_update(END_UPDATE);
	graf_mouse(M_ON, NULL);
	return ok;
}
#endif

/*
 * Function:	Handle the opened zoom window
 *
 * Parameters:	None
 *
 * Returns:		None
 *
 */
void
doZoom(void)
{
	GRECT work = {127, 127, 129, 129};
	GRECT curr;
	uchar *scr;
	short win;
	short x, y, junk;
	short ev;
	short top;
	int   done = 0;

	wind_calc(WC_BORDER, WF_PARTS, elements(work), pointers(curr));
	win = wind_create(WF_PARTS, elements(curr));
	if (win < 0)
		return;
	wind_title(win, title + 1);
	wind_open(win, elements(curr));
	graf_mkstate(&x, &y, &junk, &junk);
	zoom(x, y);
	graf_mouse(THIN_CROSS, NULL);

	do {
		ev = evnt_multi(MU_M1 | MU_MESAG,
			0, 0, 0,
			1, x, y, 1, 1,
			0, 0, 0, 0, 0,
			m,
			0, 0,
			&x, &y,
			&junk, &junk, &junk, &junk);

		wind_update(BEG_UPDATE);

		wind_get(win, WF_TOP, &top, &junk, &junk, &junk);
/*
 * It IS possible to get MU_M1 events if the window is NOT topped!!
 *
 */
		if (top == win && (ev & MU_M1)) {
			zoom(x, y);
			scr = display + (work.g_x + 1) / 8;
			scr += (long)(work.g_y + 1) * data->ld_vwrap;
			graf_mouse(M_OFF, NULL);
			qblit(scr, zoomed, data->ld_vwrap);
			graf_mouse(M_ON, NULL);
		}

		if (ev & MU_MESAG)
			switch (m[0]) {
			case WM_CLOSED:
				wind_close(win);
				wind_delete(win);	/* Fall through */
			case AC_CLOSE:
				done = 1;
				break;
			case AC_OPEN:
				wind_set(win, WF_TOP);
				break;
			case WM_TOPPED:
				if (m[3] == win) {
					wind_set(win, WF_TOP);
					graf_mouse(THIN_CROSS, NULL);
				}
				break;
			case WM_REDRAW:
				wind_redraw(win, (GRECT *)&m[4], redraw);
				break;
			case WM_MOVED:
				curr2curr((GRECT *)&m[4], &curr);
				wind_set(win, WF_CURRXYWH, elements(curr));
				wind_get(win, WF_WORKXYWH, pointers(work));
				break;
			default:
				break;
			}

		wind_update(END_UPDATE);

	} while (!done);
	graf_mouse(ARROW, NULL);
}

/*
 * Function:	Initialise GEM, install as a DA, get screen address.
 *
 * Parameters:	None
 *
 * Returns:		None (sets err global)
 *
 */
void
init(void)
{
	short workin[] = {1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2};
	short workout[57];
	short junk;
	short id;

	err = 0;
	data = linea0();
	if (data->ld_vplanes != 1)
		err = -1;

	display = (uchar *)Logbase();

	id = appl_init();
#ifdef ACCorPRG
	if (_XMODE == 2)
#endif

		err = menu_register(id, title);
#ifdef ACCorPRG
	else
		err = 0;
#endif

	screen.fd_addr = NULL;
	handle = graf_handle(&junk, &junk, &junk, &junk);
	v_opnvwk(workin, &handle, workout);
}

/*
 * Function:	Main program
 *
 * Parameters:	None
 *
 * Returns:		None (never returns if run as an accessory)
 *
 */
void
main(void)
{
	init();

#ifdef ACCorPRG
	if (_XMODE == 2) {
#endif
		if (err == -1)
			Cconws("Error installing \033pZoomAcc\033q!\r\n");
		for (;;) {
			evnt_mesag(m);
			if (err != -1 && m[0] == AC_OPEN)
				doZoom();
		}
#ifdef ACCorPRG
	} else {
		doZoom();
		v_clsvwk(handle);
		appl_exit();
	}
#endif
}

/* End of zoomacc.c */
