#include "win32\win32guts.h"
#ifndef _APRICOT_H_
#include "apricot.h"
#endif
#include "guts.h"
#include "Image.h"

#ifdef __cplusplus
extern "C" {
#endif

#define  sys (( PDrawableData)(( PComponent) self)-> sysData)->
#define  dsys( view) (( PDrawableData)(( PComponent) view)-> sysData)->
#define var (( PComponent) self)->
#define HANDLE sys handle
#define DHANDLE(x) dsys(x) handle
#define check_swap( parm1, parm2) if ( parm1 > parm2) { int parm3 = parm1; parm1 = parm2; parm2 = parm3;}

#define GET_REGION(obj) (&(dsys(obj)s.region))
#define REGION GET_REGION(self)->region
#define APERTURE GET_REGION(self)->aperture

HRGN
region_create( Handle mask)
{
	LONG w, h, x, y, size = 256;
	HRGN    rgn = NULL;
	Byte    * idata;
	RGNDATA * rdata = NULL;
	RECT    * current;
	Bool      set = 0;

	if ( !mask)
		return NULL;

	dobjCheck( mask) NULL;

	w = PImage( mask)-> w;
	h = PImage( mask)-> h;
	if ( dsys( mask) s. image. imgCachedRegion) {
		rgn = CreateRectRgn(0,0,0,0);
		CombineRgn( rgn, dsys( mask) s. image. imgCachedRegion, NULL, RGN_COPY);
		return rgn;
	}

	idata  = PImage( mask)-> data + PImage( mask)-> dataSize - PImage( mask)-> lineSize;

	rdata = ( RGNDATA*) malloc( sizeof( RGNDATAHEADER) + size * sizeof( RECT));
	if ( !rdata) {
		warn("Not enough memory");
		return NULL;
	}

	rdata-> rdh. nCount = 0;
	current = ( RECT * ) &( rdata-> Buffer);
	current--;

	for ( y = 0; y < h; y++) {
		for ( x = 0; x < w; x++) {
			if ( idata[ x >> 3] == 0) {
				x += 7;
				continue;
			}
			if ( idata[ x >> 3] & ( 1 << ( 7 - ( x & 7)))) {
				if ( set && current-> top == y && current-> right == x)
					current-> right++;
				else {
					set = 1;
					if ( rdata-> rdh. nCount >= size) {
						void * xrdata = ( RGNDATA *) realloc( rdata, sizeof( RGNDATAHEADER) + ( size *= 3) * sizeof( RECT));
						if ( !xrdata) {
							free( rdata);
							return NULL;
						}
						rdata = xrdata;
						current = ( RECT * ) &( rdata-> Buffer);
						current += rdata-> rdh. nCount - 1;
					}
					rdata-> rdh. nCount++;
					current++;
					current-> left   = x;
					current-> top    = y;
					current-> right  = x + 1;
					current-> bottom = y + 1;
				}
			}
		}
		idata -= PImage( mask)-> lineSize;
	}


	if ( set) {
		rdata-> rdh. dwSize          = sizeof( RGNDATAHEADER);
		rdata-> rdh. iType           = RDH_RECTANGLES;
		rdata-> rdh. nRgnSize        = rdata-> rdh. nCount * sizeof( RECT);
		rdata-> rdh. rcBound. left   = 0;
		rdata-> rdh. rcBound. top    = 0;
		rdata-> rdh. rcBound. right  = h;
		rdata-> rdh. rcBound. bottom = w;

		if ( !( rgn = ExtCreateRegion( NULL,
			sizeof( RGNDATAHEADER) + ( rdata-> rdh. nCount * sizeof( RECT)), rdata))) {
			apcErr( 900);
		}

		dsys( mask) s. image. imgCachedRegion = CreateRectRgn(0,0,0,0);
		CombineRgn( dsys( mask) s. image. imgCachedRegion, rgn, NULL, RGN_COPY);
	} else {
		dsys( mask) s. image. imgCachedRegion = rgn = CreateRectRgn(0,0,0,0);
	}
	free( rdata);

	return rgn;
}


static Bool
rgn_empty(Handle self)
{
	REGION = CreateRectRgn(0,0,0,0);
	APERTURE = 0;
	return true;
}

static Bool
rgn_rect(Handle self, int count, Box * r)
{
	int i, h;
	Box * rr;
	h = r->y + r->height;
	for ( i = 0, rr = r; i < count; i++, rr++) {
		if ( h < rr->y + rr->height)
			h = rr->y + rr->height;
	}
	REGION = CreateRectRgn(r->x, h - r->y - r->height, r->width+r->x, h - r->y);
	APERTURE = h;
	for ( i = 1, r++; i < count; i++, r++) {
		HRGN reg = CreateRectRgn(r->x, h - r->y - r->height, r->width+r->x, h - r->y);
		CombineRgn( REGION, REGION, reg, RGN_OR);
		DeleteObject(reg);
	}
	return true;
}

static Bool
rgn_polygon(Handle self, PolygonRegionRec * r)
{
	int i, max, open, xp_points;
	POINT * xp;

	open =
		r->points[r->n_points-1].x != r->points[0].x ||
		r->points[r->n_points-1].y != r->points[0].y;
	xp_points = r->n_points + (open ? 1 : 0);

	if ( !( xp = malloc( sizeof(POINT) * xp_points ))) {
		warn("Not enough memory");
		return false;
	}

	for ( i = 0, max = 0; i < r->n_points; i++) {
		if ( max < r->points[i].y)
			max = r->points[i].y;
	}
	max++;
	for ( i = 0; i < r->n_points; i++) {
		xp[i].x = r->points[i].x;
		xp[i].y = max - r->points[i].y - 1;
	}
	if ( open ) {
		xp[i].x = r->points[0].x;
		xp[i].y = max - r->points[0].y - 1;
	}

	APERTURE = max;
	REGION = CreatePolygonRgn( xp, r->n_points, 
		((r-> fill_mode & fmWinding) == fmAlternate) ? ALTERNATE : WINDING);
	if (( r->fill_mode & fmOverlay) == 0) goto NO_OVERLAY;

	/* superimpose polyline points using Bresenham
	because windows regions are as broken as filled shapes */
	for ( i = 0; i < xp_points-1; i++) {
		int curr_maj, curr_min, to_maj, delta_maj, delta_min;
		int delta_y, delta_x;
		int dir = 0, d, d_inc1, d_inc2;
		int inc_maj, inc_min;
		int x, y, acc_x = 0, acc_y = INT_MIN, ox;
		POINT a = xp[i], b = xp[i+1];
		delta_y = b.y - a.y;
		delta_x = b.x - a.x;
		if (abs(delta_y) > abs(delta_x)) dir = 1;

		if (dir) {
			curr_maj = a.y;
			curr_min = a.x;
			to_maj = b.y;
			delta_maj = delta_y;
			delta_min = delta_x;
		} else {
			curr_maj = a.x;
			curr_min = a.y;
			to_maj = b.x;
			delta_maj = delta_x;
			delta_min = delta_y;
		}

		if (delta_maj != 0)
			inc_maj = (abs(delta_maj)==delta_maj ? 1 : -1);
		else
			inc_maj = 0;

		if (delta_min != 0)
			inc_min = (abs(delta_min)==delta_min ? 1 : -1);
		else
			inc_min = 0;

		delta_maj = abs(delta_maj);
		delta_min = abs(delta_min);

		d      = (delta_min << 1) - delta_maj;
		d_inc1 = (delta_min << 1);
		d_inc2 = ((delta_min - delta_maj) << 1);

		while(1) {
			ox = x;
			if (dir) {
				x = curr_min;
				y = curr_maj;
			} else {
				x = curr_maj;
				y = curr_min;
			}
			if ( acc_y != y ) {
				if ( acc_y > INT_MIN) {
					HRGN reg;
					int x1, x2;
					if (ox < acc_x) {
						x1 = ox;
						x2 = acc_x;
					} else {
						x1 = acc_x;
						x2 = ox;
					}
					reg = CreateRectRgn(x1, acc_y, x2+1, acc_y + 1);
					CombineRgn( REGION, REGION, reg, RGN_OR);
					DeleteObject(reg);
				}
				acc_x = x;
				acc_y = y;
			}

			if (curr_maj == to_maj) break;
			curr_maj += inc_maj;
			if (d < 0) {
				d += d_inc1;
			} else {
				d += d_inc2;
				curr_min += inc_min;
			}
		}
		if ( acc_y > INT_MIN) {
			HRGN reg;
			int x1, x2;
			if (x < acc_x) {
				x1 = x;
				x2 = acc_x;
			} else {
				x1 = acc_x;
				x2 = x;
			}
			reg = CreateRectRgn(x1, acc_y, x2+1, acc_y + 1);
			CombineRgn( REGION, REGION, reg, RGN_OR);
			DeleteObject(reg);
		}
	}

NO_OVERLAY:
	free( xp );
	return true;
}

static Bool
rgn_image(Handle self, Handle image)
{
	REGION = region_create(image);

	if ( !REGION )
		REGION = CreateRectRgn(0,0,0,0);
	else
		APERTURE = PImage(image)->h;
	return true;
}

Bool
apc_region_create( Handle self, PRegionRec rec)
{
	switch( rec-> type ) {
	case rgnEmpty:
		return rgn_empty(self);
	case rgnRectangle:
		return rgn_rect(self, rec->data.box.n_boxes, rec->data.box.boxes);
	case rgnPolygon:
		return rgn_polygon(self, &rec->data.polygon);
	case rgnImage:
		return rgn_image(self, rec->data.image);
	default:
		return false;
	}
}

Bool
apc_region_destroy( Handle self)
{
	if (REGION) {
		DeleteObject(REGION);
		REGION = NULL;
	}
	return true;
}

Bool
apc_region_offset( Handle self, int dx, int dy)
{
	OffsetRgn(REGION, dx, -dy);
	return false;
}

static Handle ctx_rgnop2RGN[] = {
	rgnopCopy,        RGN_COPY,
	rgnopUnion,       RGN_OR,
	rgnopIntersect,   RGN_AND,
	rgnopXor,         RGN_XOR,
	rgnopDiff,        RGN_DIFF,
	endCtx
};

Bool
apc_region_combine( Handle self, Handle other_region, int rgnop)
{
	RegionData * r2;
	int d;
	Bool ok;

	r2 = GET_REGION(other_region);

	if ( rgnop == rgnopCopy ) {
		ok = CombineRgn( REGION, r2->region, NULL, RGN_COPY);
		APERTURE = r2-> aperture;
		return ok;
	}

	d = APERTURE - r2-> aperture;
	if ( d > 0 )
		OffsetRgn( r2-> region, 0, d);
	else
		OffsetRgn( REGION, 0, -d);

	rgnop = ctx_remap_def( rgnop, ctx_rgnop2RGN, true, RGN_COPY);
	ok = CombineRgn( REGION, REGION, r2->region, rgnop);

	if ( d > 0 )
		OffsetRgn( r2-> region, 0, -d);
	else
		APERTURE = r2-> aperture;

	return ok;
}

Bool
apc_region_point_inside( Handle self, Point p)
{
	return PtInRegion( REGION, p.x, APERTURE - p.y - 1);
}

int
apc_region_rect_inside( Handle self, Rect r)
{
	RECT q;
	HRGN t1, t2;
	int ret;

	q. left   = r. left;
	q. top    = APERTURE - r. bottom;
	q. right  = r. right + 1;
	q. bottom = APERTURE - r. top - 1;
	if ( RectInRegion( REGION, &q) == 0 ) return rgnOutside;

	t1 = CreateRectRgnIndirect(&q);
	t2 = CreateRectRgnIndirect(&q);

	CombineRgn( t2, REGION, t1, RGN_AND);
	ret = EqualRgn( t1, t2 ) ? rgnInside : rgnPartially;

	DeleteObject(t2);
	DeleteObject(t1);
	return ret;
}

Bool
apc_region_equals( Handle self, Handle other_region)
{
	return EqualRgn( REGION, GET_REGION(other_region)->region);
}

Bool
apc_region_is_empty( Handle self)
{
	RECT xr;
	return GetRgnBox( REGION, &xr) == NULLREGION;
}

Box
apc_region_get_box( Handle self)
{
	Box box;
	RECT xr;
	if ( GetRgnBox( REGION, &xr) == NULLREGION) {
		memset(&box, 0, sizeof(box));
		return box;
	}
	box. x      = xr. left;
	box. y      = APERTURE - xr. bottom;
	box. width  = xr. right - xr. left;
	box. height = xr. bottom - xr. top;
	return box;
}

ApiHandle
apc_region_get_handle( Handle self)
{
	return (ApiHandle) REGION;
}

Rect
apc_gp_get_clip_rect( Handle self)
{
	RECT r;
	Point p;
	Rect rr = {0,0,0,0};
	objCheck rr;
	if ( !is_opt( optInDraw) || !sys ps) return rr;
	if ( !GetClipBox( sys ps, &r)) apiErr;
	if ( IsRectEmpty( &r)) return rr;
	rr. left   = r. left;
	rr. right  = r. right - 1;
	rr. bottom = sys lastSize. y - r. bottom;
	rr. top    = sys lastSize. y - r. top    - 1;

	p = apc_gp_get_transform(self);
	rr. left   += p.x;
	rr. right  += p.x;
	rr. top    += p.y;
	rr. bottom += p.y;

	rr. left   += sys transform2. x;
	rr. right  += sys transform2. x;
	rr. top    -= sys transform2. y;
	rr. bottom -= sys transform2. y;

	return rr;
}

Bool
apc_gp_set_clip_rect( Handle self, Rect c)
{
	HRGN rgn;
	objCheck false;
	if ( !is_opt( optInDraw) || !sys ps) return true;
	// inclusive-exclusive
	c. left   -= sys transform2. x;
	c. right  -= sys transform2. x;
	c. top    += sys transform2. y;
	c. bottom += sys transform2. y;
	check_swap( c. top, c. bottom);
	check_swap( c. left, c. right);
	if ( !( rgn = CreateRectRgn(
		c. left,  sys lastSize. y - c. top,
		c. right + 1, sys lastSize. y - c. bottom - 1))
		) apiErrRet;
	if ( is_apt(aptLayeredPaint) && sys layeredParentRegion )
		CombineRgn( rgn, rgn, sys layeredParentRegion, RGN_AND);
	if ( !SelectClipRgn( sys ps, rgn)) apiErr;
	if ( sys graphics ) {
		GPCALL GdipSetClipHrgn(sys graphics, rgn, CombineModeReplace);
		apiGPErrCheck;
	}
	if ( !DeleteObject( rgn)) apiErr;
	return true;
}

Bool
apc_gp_get_region( Handle self, Handle mask)
{
	HRGN rgn;
	RECT rect;
	int res;

	objCheck false;
	if ( !is_opt( optInDraw) || !sys ps) return false;

	if ( !mask ) {
		rgn = CreateRectRgn(0,0,0,0);
		res = GetClipRgn( sys ps, rgn );
		DeleteObject(rgn);
		return res > 0;
	}

	rgn = GET_REGION(mask)-> region;
	res = GetClipRgn( sys ps, rgn );
	if ( res <= 0)        // error or just no region
		return false;
	GetRgnBox(rgn, &rect);
	OffsetRgn( rgn, sys transform2. x, sys transform2. y - rect.top);
	GET_REGION(mask)-> aperture = sys lastSize. y - rect.top;
	return true;
}

Bool
apc_gp_set_region( Handle self, Handle region)
{
	HRGN rgn = NULL;
	objCheck false;

	if ( !is_opt( optInDraw) || !sys ps) return true;

	if ( !region) {
		SelectClipRgn( sys ps, NULL);
		return true;
	}
	rgn = CreateRectRgn(0,0,0,0);
	CombineRgn(rgn, GET_REGION(region)->region, NULL, RGN_COPY);
	OffsetRgn( rgn, -sys transform2. x, -sys transform2. y);
	OffsetRgn( rgn, 0, sys lastSize.y - GET_REGION(region)->aperture);
	if ( is_apt(aptLayeredPaint) && sys layeredParentRegion )
		CombineRgn( rgn, rgn, sys layeredParentRegion, RGN_AND);
	SelectClipRgn( sys ps, rgn);
	if ( sys graphics ) {
		GPCALL GdipSetClipHrgn(sys graphics, rgn, CombineModeReplace);
		apiGPErrCheck;
	}
	DeleteObject( rgn);
	return true;
}

PRegionRec
apc_region_copy_rects( Handle self)
{
	int i, aperture, size;
	PRegionRec ret;
	Box *dst;
	RECT *src;
	RGNDATA *rgndata;

	size = GetRegionData( REGION, 0, NULL);
	if ( !( rgndata = malloc(size))) {
		warn("Not enough memory\n");
		return NULL;
	}
	size = GetRegionData( REGION, size, rgndata);
	if ( size == 0) return NULL;

	ret = malloc(sizeof(RegionRec) + sizeof(Box) * rgndata-> rdh. nCount );
	if ( ret == NULL ) {
		free(ret);
		warn("Not enough memory\n");
		return NULL;
	}

	ret-> type = rgnRectangle;
	ret-> data. box. n_boxes = rgndata->rdh. nCount;
	src = (RECT*) &(rgndata->Buffer);
	dst = ret-> data. box. boxes = (Box*) (((Byte*)ret) + sizeof(RegionRec));
	aperture = APERTURE;
	for ( i = 0; i < ret->data. box. n_boxes; i++, src++, dst++) {
		dst-> x = src-> left;
		dst-> y = aperture - src-> bottom;
		dst-> width  = src-> right - src->left;
		dst-> height = src-> bottom - src->top;
		/* printf("%d: %ld %ld %ld %ld => %d %d %d %d\n", aperture, src->left, src->top, src->right, src->bottom, dst->x, dst->y, dst-> width, dst->height); */
	}
	free(rgndata);

	return ret;
}

#ifdef __cplusplus
}
#endif