/**
 * FreeRDP: A Remote Desktop Protocol Implementation
 * GDI Region Functions
 *
 * Copyright 2010-2011 Marc-Andre Moreau <marcandre.moreau@gmail.com>
 * Copyright 2016 Armin Novak <armin.novak@thincast.com>
 * Copyright 2016 Thincast Technologies GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <freerdp/config.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <winpr/wtypes.h>

#include <freerdp/api.h>
#include <freerdp/freerdp.h>
#include <freerdp/gdi/gdi.h>

#include <freerdp/gdi/region.h>

#include <freerdp/log.h>

#define TAG FREERDP_TAG("gdi.region")

static char* gdi_rect_str(char* buffer, size_t size, const GDI_RECT* rect)
{
	if (!buffer || (size < 1) || !rect)
		return nullptr;

	(void)_snprintf(buffer, size - 1,
	                "[top/left=%" PRId32 "x%" PRId32 "-bottom/right%" PRId32 "x%" PRId32 "]",
	                rect->top, rect->left, rect->bottom, rect->right);
	buffer[size - 1] = '\0';

	return buffer;
}

static char* gdi_regn_str(char* buffer, size_t size, const GDI_RGN* rgn)
{
	if (!buffer || (size < 1) || !rgn)
		return nullptr;

	(void)_snprintf(buffer, size - 1, "[%" PRId32 "x%" PRId32 "-%" PRId32 "x%" PRId32 "]", rgn->x,
	                rgn->y, rgn->w, rgn->h);
	buffer[size - 1] = '\0';

	return buffer;
}

/**
 * Create a region from rectangular coordinates.
 * msdn{dd183514}
 *
 * @param nLeftRect x1
 * @param nTopRect y1
 * @param nRightRect x2
 * @param nBottomRect y2
 *
 * @return new region
 */

GDI_RGN* gdi_CreateRectRgn(INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect, INT32 nBottomRect)
{
	INT64 w = 0;
	INT64 h = 0;
	GDI_RGN* hRgn = nullptr;

	w = nRightRect - nLeftRect + 1ll;
	h = nBottomRect - nTopRect + 1ll;
	if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
	{
		WLog_ERR(TAG,
		         "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
		         "x%" PRId32,
		         nTopRect, nLeftRect, nBottomRect, nRightRect);
		return nullptr;
	}
	hRgn = (GDI_RGN*)calloc(1, sizeof(GDI_RGN));

	if (!hRgn)
		return nullptr;

	hRgn->objectType = GDIOBJECT_REGION;
	hRgn->x = nLeftRect;
	hRgn->y = nTopRect;
	hRgn->w = (INT32)w;
	hRgn->h = (INT32)h;
	hRgn->null = FALSE;
	return hRgn;
}

/**
 * Create a new rectangle.
 * @param xLeft x1
 * @param yTop y1
 * @param xRight x2
 * @param yBottom y2
 * @return new rectangle
 */

GDI_RECT* gdi_CreateRect(INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
{
	GDI_RECT* hRect = nullptr;

	if (xLeft > xRight)
		return nullptr;
	if (yTop > yBottom)
		return nullptr;

	hRect = (GDI_RECT*)calloc(1, sizeof(GDI_RECT));

	if (!hRect)
		return nullptr;

	hRect->objectType = GDIOBJECT_RECT;
	hRect->left = xLeft;
	hRect->top = yTop;
	hRect->right = xRight;
	hRect->bottom = yBottom;
	return hRect;
}

/**
 * Convert a rectangle to a region.
 * @param rect source rectangle
 * @param rgn destination region
 */

BOOL gdi_RectToRgn(const GDI_RECT* rect, GDI_RGN* rgn)
{
	WINPR_ASSERT(rect);
	WINPR_ASSERT(rgn);

	BOOL rc = TRUE;
	INT64 w = rect->right - rect->left + 1ll;
	INT64 h = rect->bottom - rect->top + 1ll;

	if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
	{
		WLog_ERR(TAG,
		         "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
		         "x%" PRId32,
		         rect->top, rect->left, rect->bottom, rect->right);
		w = 0;
		h = 0;
		rc = FALSE;
	}

	rgn->x = rect->left;
	rgn->y = rect->top;
	rgn->w = (INT32)w;
	rgn->h = (INT32)h;

	return rc;
}

/**
 * Convert rectangular coordinates to a region.
 * @param left x1
 * @param top y1
 * @param right x2
 * @param bottom y2
 * @param rgn destination region
 */

BOOL gdi_CRectToRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, GDI_RGN* rgn)
{
	BOOL rc = TRUE;
	INT64 w = 0;
	INT64 h = 0;
	w = right - left + 1ll;
	h = bottom - top + 1ll;

	if (!rgn)
		return FALSE;

	if ((w < 0) || (h < 0) || (w > INT32_MAX) || (h > INT32_MAX))
	{
		WLog_ERR(TAG,
		         "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
		         "x%" PRId32,
		         top, left, bottom, right);
		w = 0;
		h = 0;
		rc = FALSE;
	}

	rgn->x = left;
	rgn->y = top;
	rgn->w = (INT32)w;
	rgn->h = (INT32)h;
	return rc;
}

/**
 * Convert a rectangle to region coordinates.
 * @param rect source rectangle
 * @param x x1
 * @param y y1
 * @param w width
 * @param h height
 */

BOOL gdi_RectToCRgn(const GDI_RECT* rect, INT32* x, INT32* y, INT32* w, INT32* h)
{
	BOOL rc = TRUE;
	*x = rect->left;
	*y = rect->top;
	INT64 tmp = rect->right - rect->left + 1;
	if ((tmp < 0) || (tmp > INT32_MAX))
	{
		char buffer[256];
		WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
		*w = 0;
		rc = FALSE;
	}
	else
		*w = (INT32)tmp;
	tmp = rect->bottom - rect->top + 1;
	if ((tmp < 0) || (tmp > INT32_MAX))
	{
		char buffer[256];
		WLog_ERR(TAG, "rectangle invalid %s", gdi_rect_str(buffer, sizeof(buffer), rect));
		*h = 0;
		rc = FALSE;
	}
	else
		*h = (INT32)tmp;
	return rc;
}

/**
 * Convert rectangular coordinates to region coordinates.
 * @param left x1
 * @param top y1
 * @param right x2
 * @param bottom y2
 * @param x x1
 * @param y y1
 * @param w width
 * @param h height
 */

BOOL gdi_CRectToCRgn(INT32 left, INT32 top, INT32 right, INT32 bottom, INT32* x, INT32* y, INT32* w,
                     INT32* h)
{
	INT64 wl = 0;
	INT64 hl = 0;
	BOOL rc = TRUE;
	wl = right - left + 1ll;
	hl = bottom - top + 1ll;

	if ((left > right) || (top > bottom) || (wl <= 0) || (hl <= 0) || (wl > INT32_MAX) ||
	    (hl > INT32_MAX))
	{
		WLog_ERR(TAG,
		         "Can not create region top/left=%" PRId32 "x%" PRId32 "-bottom/right=%" PRId32
		         "x%" PRId32,
		         top, left, bottom, right);
		wl = 0;
		hl = 0;
		rc = FALSE;
	}

	*x = left;
	*y = top;
	*w = (INT32)wl;
	*h = (INT32)hl;
	return rc;
}

/**
 * Convert a region to a rectangle.
 * @param rgn source region
 * @param rect destination rectangle
 */

BOOL gdi_RgnToRect(const GDI_RGN* rgn, GDI_RECT* rect)
{
	INT64 r = 0;
	INT64 b = 0;
	BOOL rc = TRUE;
	r = rgn->x + rgn->w - 1ll;
	b = rgn->y + rgn->h - 1ll;

	if ((r < INT32_MIN) || (r > INT32_MAX) || (b < INT32_MIN) || (b > INT32_MAX))
	{
		char buffer[256];
		WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
		r = rgn->x;
		b = rgn->y;
		rc = FALSE;
	}
	rect->left = rgn->x;
	rect->top = rgn->y;
	rect->right = (INT32)r;
	rect->bottom = (INT32)b;

	return rc;
}

/**
 * Convert region coordinates to a rectangle.
 * @param x x1
 * @param y y1
 * @param w width
 * @param h height
 * @param rect destination rectangle
 */

BOOL gdi_CRgnToRect(INT64 x, INT64 y, INT32 w, INT32 h, GDI_RECT* rect)
{
	BOOL invalid = FALSE;
	const INT64 r = x + w - 1;
	const INT64 b = y + h - 1;
	WINPR_ASSERT(x <= INT32_MAX);
	WINPR_ASSERT(y <= INT32_MAX);
	WINPR_ASSERT(r <= INT32_MAX);
	WINPR_ASSERT(b <= INT32_MAX);
	rect->left = (x > 0) ? (INT32)x : 0;
	rect->top = (y > 0) ? (INT32)y : 0;
	rect->right = rect->left;
	rect->bottom = rect->top;

	if ((w <= 0) || (h <= 0))
		invalid = TRUE;

	if (r > 0)
		rect->right = (INT32)r;
	else
		invalid = TRUE;

	if (b > 0)
		rect->bottom = (INT32)b;
	else
		invalid = TRUE;

	if (invalid)
	{
		WLog_DBG(TAG, "Invisible rectangle %" PRId64 "x%" PRId64 "-%" PRId64 "x%" PRId64, x, y, r,
		         b);
		return FALSE;
	}

	return TRUE;
}

/**
 * Convert a region to rectangular coordinates.
 * @param rgn source region
 * @param left x1
 * @param top y1
 * @param right x2
 * @param bottom y2
 */

BOOL gdi_RgnToCRect(const GDI_RGN* rgn, INT32* left, INT32* top, INT32* right, INT32* bottom)
{
	BOOL rc = TRUE;

	WINPR_ASSERT(rgn);
	if ((rgn->w < 0) || (rgn->h < 0))
	{
		char buffer[256];
		WLog_ERR(TAG, "Can not create region %s", gdi_regn_str(buffer, sizeof(buffer), rgn));
		rc = FALSE;
	}

	*left = rgn->x;
	*top = rgn->y;
	*right = rgn->x + rgn->w - 1;
	*bottom = rgn->y + rgn->h - 1;

	return rc;
}

/**
 * Convert region coordinates to rectangular coordinates.
 * @param x x1
 * @param y y1
 * @param w width
 * @param h height
 * @param left x1
 * @param top y1
 * @param right x2
 * @param bottom y2
 */

BOOL gdi_CRgnToCRect(INT32 x, INT32 y, INT32 w, INT32 h, INT32* left, INT32* top, INT32* right,
                     INT32* bottom)
{
	BOOL rc = TRUE;
	*left = x;
	*top = y;
	*right = 0;

	if (w > 0)
		*right = x + w - 1;
	else
	{
		WLog_ERR(TAG, "Invalid width");
		rc = FALSE;
	}

	*bottom = 0;

	if (h > 0)
		*bottom = y + h - 1;
	else
	{
		WLog_ERR(TAG, "Invalid height");
		rc = FALSE;
	}

	return rc;
}

/**
 * Check if copying would involve overlapping regions
 * @param x x1
 * @param y y1
 * @param width width
 * @param height height
 * @param srcx source x1
 * @param srcy source y1
 * @return nonzero if there is an overlap, 0 otherwise
 */

inline BOOL gdi_CopyOverlap(INT32 x, INT32 y, INT32 width, INT32 height, INT32 srcx, INT32 srcy)
{
	GDI_RECT dst;
	GDI_RECT src;
	if (!gdi_CRgnToRect(x, y, width, height, &dst))
		return FALSE;
	if (!gdi_CRgnToRect(srcx, srcy, width, height, &src))
		return FALSE;

	if (dst.right < src.left)
		return FALSE;
	if (dst.left > src.right)
		return FALSE;
	if (dst.bottom < src.top)
		return FALSE;
	if (dst.top > src.bottom)
		return FALSE;
	return TRUE;
}

/**
 * Set the coordinates of a given rectangle.
 * msdn{dd145085}
 *
 * @param rc rectangle
 * @param xLeft x1
 * @param yTop y1
 * @param xRight x2
 * @param yBottom y2
 *
 * @return nonzero if successful, 0 otherwise
 */

inline BOOL gdi_SetRect(GDI_RECT* rc, INT32 xLeft, INT32 yTop, INT32 xRight, INT32 yBottom)
{
	if (!rc)
		return FALSE;
	if (xLeft > xRight)
		return FALSE;
	if (yTop > yBottom)
		return FALSE;

	rc->left = xLeft;
	rc->top = yTop;
	rc->right = xRight;
	rc->bottom = yBottom;
	return TRUE;
}

/**
 * Set the coordinates of a given region.
 * @param hRgn region
 * @param nXLeft x1
 * @param nYLeft y1
 * @param nWidth width
 * @param nHeight height
 * @return nonzero if successful, 0 otherwise
 */

inline BOOL gdi_SetRgn(GDI_RGN* hRgn, INT32 nXLeft, INT32 nYLeft, INT32 nWidth, INT32 nHeight)
{
	if (!hRgn)
		return FALSE;

	if ((nWidth < 0) || (nHeight < 0))
		return FALSE;

	hRgn->x = nXLeft;
	hRgn->y = nYLeft;
	hRgn->w = nWidth;
	hRgn->h = nHeight;
	hRgn->null = FALSE;
	return TRUE;
}

/**
 * Convert rectangular coordinates to a region
 * @param hRgn destination region
 * @param nLeftRect x1
 * @param nTopRect y1
 * @param nRightRect x2
 * @param nBottomRect y2
 * @return nonzero if successful, 0 otherwise
 */

inline BOOL gdi_SetRectRgn(GDI_RGN* hRgn, INT32 nLeftRect, INT32 nTopRect, INT32 nRightRect,
                           INT32 nBottomRect)
{
	if (!gdi_CRectToRgn(nLeftRect, nTopRect, nRightRect, nBottomRect, hRgn))
		return FALSE;
	hRgn->null = FALSE;
	return TRUE;
}

/**
 * @brief Compare two regions for equality.
 * msdn{dd162700}
 *
 * @param hSrcRgn1 first region
 * @param hSrcRgn2 second region
 * @return nonzero if both regions are equal, 0 otherwise
 */

inline BOOL gdi_EqualRgn(const GDI_RGN* hSrcRgn1, const GDI_RGN* hSrcRgn2)
{
	WINPR_ASSERT(hSrcRgn1);
	WINPR_ASSERT(hSrcRgn2);
	return ((hSrcRgn1->x == hSrcRgn2->x) && (hSrcRgn1->y == hSrcRgn2->y) &&
	        (hSrcRgn1->w == hSrcRgn2->w) && (hSrcRgn1->h == hSrcRgn2->h));
}

/**
 * @brief Copy coordinates from a rectangle to another rectangle
 * msdn{dd183481}
 *
 * @param dst destination rectangle
 * @param src source rectangle
 * @return nonzero if successful, 0 otherwise
 */

inline BOOL gdi_CopyRect(GDI_RECT* dst, const GDI_RECT* src)
{
	if (!dst || !src)
		return FALSE;

	dst->left = src->left;
	dst->top = src->top;
	dst->right = src->right;
	dst->bottom = src->bottom;
	return TRUE;
}

/**
 * Check if a point is inside a rectangle.
 * msdn{dd162882}
 * @param rc rectangle
 * @param x point x position
 * @param y point y position
 * @return nonzero if the point is inside, 0 otherwise
 */

inline BOOL gdi_PtInRect(const GDI_RECT* rc, INT32 x, INT32 y)
{
	/*
	 * points on the left and top sides are considered in,
	 * while points on the right and bottom sides are considered out
	 */
	if ((x >= rc->left) && (x <= rc->right))
	{
		if ((y >= rc->top) && (y <= rc->bottom))
		{
			return TRUE;
		}
	}

	return FALSE;
}

/**
 * Invalidate a given region, such that it is redrawn on the next region update.
 * msdn{dd145003}
 * @param hdc device context
 * @param x x1
 * @param y y1
 * @param w width
 * @param h height
 * @return nonzero on success, 0 otherwise
 */

inline BOOL gdi_InvalidateRegion(HGDI_DC hdc, INT32 x, INT32 y, INT32 w, INT32 h)
{
	GDI_RECT inv = WINPR_C_ARRAY_INIT;
	GDI_RECT rgn = WINPR_C_ARRAY_INIT;

	if (!hdc->hwnd)
		return TRUE;

	if (!hdc->hwnd->invalid)
		return TRUE;

	if (w == 0 || h == 0)
		return TRUE;

	GDI_RGN* cinvalid = hdc->hwnd->cinvalid;

	if ((hdc->hwnd->ninvalid + 1) > (INT64)hdc->hwnd->count)
	{
		GDI_RGN* new_rgn = nullptr;
		size_t new_cnt = 2ULL * hdc->hwnd->count;
		if (new_cnt > UINT32_MAX)
			return FALSE;

		new_rgn = (GDI_RGN*)realloc(cinvalid, sizeof(GDI_RGN) * new_cnt);

		if (!new_rgn)
			return FALSE;

		hdc->hwnd->count = (UINT32)new_cnt;
		cinvalid = new_rgn;
	}

	hdc->hwnd->cinvalid = cinvalid;
	GDI_RGN* invalid = hdc->hwnd->invalid;

	if (!gdi_SetRgn(&cinvalid[hdc->hwnd->ninvalid++], x, y, w, h))
		return FALSE;

	if (!gdi_CRgnToRect(x, y, w, h, &rgn))
	{
		invalid->x = 0;
		invalid->y = 0;
		invalid->w = 0;
		invalid->h = 0;
		invalid->null = TRUE;
		return TRUE;
	}

	if (invalid->null)
	{
		invalid->x = x;
		invalid->y = y;
		invalid->w = w;
		invalid->h = h;
		invalid->null = FALSE;
		return TRUE;
	}

	if (!gdi_RgnToRect(invalid, &inv))
		return FALSE;

	if (rgn.left < inv.left)
		inv.left = rgn.left;

	if (rgn.top < inv.top)
		inv.top = rgn.top;

	if (rgn.right > inv.right)
		inv.right = rgn.right;

	if (rgn.bottom > inv.bottom)
		inv.bottom = rgn.bottom;

	return gdi_RectToRgn(&inv, invalid);
}
