/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "m4/gui/gui_vmng_rectangles.h"
#include "m4/gui/gui_vmng.h"
#include "m4/core/errors.h"
#include "m4/core/imath.h"
#include "m4/mem/mem.h"
#include "m4/vars.h"

namespace M4 {


#define LEFT_EDGE 1
#define RIGHT_EDGE 2

RectList *vmng_CreateNewRect(int32 x1, int32 y1, int32 x2, int32 y2) {
	RectList *newRect;
	if ((newRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectList")) == nullptr) {
		error_show(FL, 'OOS!', "vmng_CreateNewRect");
	}

	newRect->x1 = x1;
	newRect->y1 = y1;
	newRect->x2 = x2;
	newRect->y2 = y2;
	newRect->next = nullptr;
	newRect->prev = nullptr;

	return newRect;
}

void vmng_AddRectToRectList(RectList **theRectList, int32 rectX1, int32 rectY1, int32 rectX2, int32 rectY2) {
	// First make sure we have a valid rectangle
	if ((rectX1 > rectX2) || (rectY1 > rectY2)) {
		return;
	}

	// Initialize the dirty rect list
	RectList *dirtyRectList = vmng_CreateNewRect(rectX1, rectY1, rectX2, rectY2);

	// Initialize the clean rectList
	RectList *cleanRectList = nullptr;
	RectList *endCleanRectList = nullptr;

	// Use a local var for theRectlist
	RectList *myRectList = *theRectList;

	// Loop through all the dirtyRects
	RectList *dirtyRect = dirtyRectList;
	while (dirtyRect) {
		// Remove dirtyRect from the head of the dirtyRectList
		dirtyRectList = dirtyRectList->next;

		// Set the intersected flag
		bool intersected = false;

		// Loop on through
		RectList *myRect = myRectList;
		while (myRect) {

			// If the two rectangles intersect
			if ((dirtyRect->x1 <= myRect->x2) && (dirtyRect->x2 >= myRect->x1) && (dirtyRect->y1 <= myRect->y2) && (dirtyRect->y2 >= myRect->y1)) {
				// Set the intersected flag
				intersected = true;

				// If dirtyRect is not completely contained within myRect
				if ((dirtyRect->x1 < myRect->x1) || (dirtyRect->y1 < myRect->y1) || (dirtyRect->x2 > myRect->x2) || (dirtyRect->y2 > myRect->y2)) {

					// First remove it from the list
					if (myRect->prev) {
						myRect->prev->next = myRect->next;
					} else {
						myRectList = myRect->next;
					}
					if (myRect->next) {
						myRect->next->prev = myRect->prev;
					}

					RectList *newRect;
					// So now there is an intersection.
					// If myRect sticks out above dirtyRect, chop it off and put it in the main rect list, to be recheck by other dirty rects
					if (myRect->y1 < dirtyRect->y1) {
						newRect = vmng_CreateNewRect(myRect->x1, myRect->y1, myRect->x2, dirtyRect->y1 - 1);
						newRect->prev = nullptr;
						newRect->next = myRectList;
						if (myRectList) {
							myRectList->prev = newRect;
						}
						myRectList = newRect;

						// And set the top of myRect to be the same as dirtyRect
						myRect->y1 = dirtyRect->y1;
					} else if (dirtyRect->y1 < myRect->y1) {
						// else if dirtyRect sticks out above chop it off and put it on the dirty list
						newRect = vmng_CreateNewRect(dirtyRect->x1, dirtyRect->y1, dirtyRect->x2, myRect->y1 - 1);
						newRect->next = dirtyRectList;
						dirtyRectList = newRect;

						// and set the top of dirtyRect to be the same as myRect
						dirtyRect->y1 = myRect->y1;
					}

					// If myRect sticks out below dirtyRect, chop it off and put it in the main rect list, to be recheck by other dirty rects
					if (myRect->y2 > dirtyRect->y2) {
						newRect = vmng_CreateNewRect(myRect->x1, dirtyRect->y2 + 1, myRect->x2, myRect->y2);
						newRect->prev = nullptr;
						newRect->next = myRectList;
						if (myRectList) {
							myRectList->prev = newRect;
						}
						myRectList = newRect;

						// and set the bottom of myRect to be the same as dirtyRect
						myRect->y2 = dirtyRect->y2;
					} else if (dirtyRect->y2 > myRect->y2) {
						//else if dirtyRect sticks out below myRect...
						newRect = vmng_CreateNewRect(dirtyRect->x1, myRect->y2 + 1, dirtyRect->x2, dirtyRect->y2);
						newRect->next = dirtyRectList;
						dirtyRectList = newRect;

						// and set the bottom of dirtyRect to be the same as myRect
						dirtyRect->y2 = myRect->y2;
					}

					// Now we've got overlapping rectangles which are the same height. create one max width one
					// If the dirtyRect sticks out on either side, the resulting rect is still dirty, otherwise clean
					if ((dirtyRect->x1 < myRect->x1) || (dirtyRect->x2 > myRect->x2)) {

						// Use dirtyRect to become the max width rect
						dirtyRect->x1 = imath_min(dirtyRect->x1, myRect->x1);
						dirtyRect->x2 = imath_max(dirtyRect->x2, myRect->x2);
						dirtyRect->next = dirtyRectList;
						dirtyRectList = dirtyRect;

						// And turf myRect
						mem_free_to_stash(myRect, _G(memtypeRECT));
					} else {
						// Else we can put what's left of myRect onto the clean list and turf dirtyRect
						// Note: it is impossible to split the dirtyRect list vertically, they always stretch horizontally,
						// therefore if this dirty rect does not stick out the sides, what's left of myRect is clean
						myRect->prev = nullptr;
						myRect->next = cleanRectList;
						if (cleanRectList) {
							cleanRectList->prev = myRect;
						} else {
							endCleanRectList = myRect;
						}
						cleanRectList = myRect;

						mem_free_to_stash((void *)dirtyRect, _G(memtypeRECT));
					}

					// Exit the loop
					myRect = nullptr;
				} else {
					// else through away dirtyRect, and get the next dirtyRect
					mem_free_to_stash(dirtyRect, _G(memtypeRECT));
					myRect = nullptr;
				}
			} else {
				// else get the next rect
				myRect = myRect->next;
			}
		}

		//if we didn't intersect, put the dirtyRect on the clean list
		if (!intersected) {
			dirtyRect->prev = nullptr;
			dirtyRect->next = cleanRectList;
			if (cleanRectList) {
				cleanRectList->prev = dirtyRect;
			} else {
				endCleanRectList = dirtyRect;
			}
			cleanRectList = dirtyRect;
		}

		// get the next dirty rect
		dirtyRect = dirtyRectList;
	}

	// now, just add the clean list onto the dirty list
	if (cleanRectList) {

		//now add the entire cleanRectList to the front of myRectList
		endCleanRectList->next = myRectList;
		if (myRectList) {
			myRectList->prev = endCleanRectList;
		}
		myRectList = cleanRectList;
	}

	// Return the rect list
	*theRectList = myRectList;
}

RectList *vmng_DuplicateRectList(RectList *myRectList) {
	RectList *newRectList = nullptr;
	RectList *prevRect = nullptr;
	RectList *myRect = myRectList;
	while (myRect) {

		// Duplicate myRect and stick it on the newRectList
		RectList *tempRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectList");
		if (tempRect == nullptr) {
			error_show(FL, 'OOS!', "vmng_DuplicateRectList()");
		}
		tempRect->x1 = myRect->x1;
		tempRect->y1 = myRect->y1;
		tempRect->x2 = myRect->x2;
		tempRect->y2 = myRect->y2;
		tempRect->prev = prevRect;
		tempRect->next = nullptr;
		if (prevRect) {
			prevRect->next = tempRect;
		} else {
			newRectList = tempRect;
		}
		prevRect = tempRect;

		//get the next rectangle
		myRect = myRect->next;
	}

	return newRectList;
}

bool vmng_RectIntersectsRectList(RectList *myRectList, int32 x1, int32 y1, int32 x2, int32 y2) {
	// Parameter verification
	if ((!myRectList) || (x1 > x2) || (y1 > y2)) {
		return false;
	}

	// Loop through the list, and break as soon as there is an intersection
	RectList *myRect = myRectList;
	while (myRect) {
		// Calculate the intersection
		const int32 intrX1 = imath_max(myRect->x1, x1);
		const int32 intrY1 = imath_max(myRect->y1, y1);
		const int32 intrX2 = imath_min(myRect->x2, x2);
		const int32 intrY2 = imath_min(myRect->y2, y2);

		// If we intersected, return true
		if ((intrX1 <= intrX2) && (intrY1 <= intrY2)) {
			return true;
		}

		// else get the next rect in the list
		myRect = myRect->next;
	}

	// We made it through the entire list with no intersections - return false
	return false;
}

bool vmng_ClipRectList(RectList **myRectList, int32 clipX1, int32 clipY1, int32 clipX2, int32 clipY2) {
	// Loop through myRect list
	RectList *myRect = *myRectList;
	while (myRect) {

		// Set the next rect
		RectList *nextRect = myRect->next;

		// Clip myRect
		const int32 x1 = imath_max(myRect->x1, clipX1);
		const int32 y1 = imath_max(myRect->y1, clipY1);
		const int32 x2 = imath_min(myRect->x2, clipX2);
		const int32 y2 = imath_min(myRect->y2, clipY2);

		// If we have a valid rectangle
		if ((x1 <= x2) && (y1 <= y2)) {
			// Clip the rectangle
			myRect->x1 = x1;
			myRect->y1 = y1;
			myRect->x2 = x2;
			myRect->y2 = y2;
		} else {
			// Else remove it from the rectList and turf it
			if (myRect->prev) {
				myRect->prev->next = myRect->next;
			} else {
				*myRectList = myRect->next;
			}
			if (myRect->next) {
				myRect->next->prev = myRect->prev;
			}

			mem_free_to_stash((void *)myRect, _G(memtypeRECT));
		}

		// Check the next rect
		myRect = nextRect;
	}

	return true;
}

bool vmng_RectListValid(RectList *myRectList) {
	RectList *myRect = myRectList;
	while (myRect) {
		RectList *tempRectList = myRect->next;
		if (vmng_RectIntersectsRectList(tempRectList, myRect->x1, myRect->y1, myRect->x2, myRect->y2)) {
			return false;
		}
		myRect = myRect->next;
	}

	return true;
}

void vmng_DisposeRectList(RectList **rectList) {
	// Loop through the rect list
	RectList *myRect = *rectList;
	while (myRect) {
		// Remove myRect from the head of the list
		*rectList = myRect->next;

		// Dispose of myRect;
		mem_free_to_stash((void *)myRect, _G(memtypeRECT));

		// Get the next rectangle
		myRect = *rectList;
	}
}

void vmng_RemoveRectFromRectList(RectList **scrnRectList, int32 x1, int32 y1, int32 x2, int32 y2) {
	RectList *tempRect;
	RectList *rectList = *scrnRectList;

	// Go through the rectList list breaking down any rects which intersect the given coords
	RectList *unsortedRectList = nullptr;
	RectList *myRect = rectList;

	while (myRect) {
		// Set the nextRect pointer
		RectList *nextRect = myRect->next;

		// Check for an intersection
		const int32 tempX1 = imath_max(x1, myRect->x1);
		const int32 tempY1 = imath_max(y1, myRect->y1);
		const int32 tempX2 = imath_min(x2, myRect->x2);
		const int32 tempY2 = imath_min(y2, myRect->y2);

		// If we have an intersection
		if ((tempX1 <= tempX2) && (tempY1 <= tempY2)) {
			// Break myRect apart into any pieces not covered by x1, y1, x2, y2
			// Top edge
			if (myRect->y1 < y1) {
				// Create a new rect of just the part that extends beyond the top of myRect
				tempRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectangle");
				if (tempRect == nullptr) {
					error_show(FL, 'OOS!', "vmng_AddRectToRectList");
				}
				tempRect->x1 = myRect->x1;
				tempRect->y1 = myRect->y1;
				tempRect->x2 = myRect->x2;
				tempRect->y2 = y1 - 1;

				// Add tempRect to the unsortedRectList
				tempRect->next = unsortedRectList;
				unsortedRectList = tempRect;

				// Update myRect
				myRect->y1 = y1;
			}

			// Bottom edge
			if (myRect->y2 > y2) {
				// Create a new rect of just the part that extends beyond the top of myRect
				tempRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectangle");
				if (tempRect == nullptr) {
					error_show(FL, 'OOS!', "vmng_AddRectToRectList");
				}
				tempRect->x1 = myRect->x1;
				tempRect->y1 = y2 + 1;
				tempRect->x2 = myRect->x2;
				tempRect->y2 = myRect->y2;

				// Add tempRect to the unsortedRectList
				tempRect->next = unsortedRectList;
				unsortedRectList = tempRect;

				// Update myRect
				myRect->y2 = y2;
			}

			// Left edge
			if (myRect->x1 < x1) {
				// Create a new rect of just the part that extends beyond the top of myRect
				tempRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectangle");
				if (tempRect == nullptr) {
					error_show(FL, 'OOS!', "vmng_AddRectToRectList");
				}
				tempRect->x1 = myRect->x1;
				tempRect->y1 = myRect->y1;
				tempRect->x2 = x1 - 1;
				tempRect->y2 = myRect->y2;

				// Add tempRect to the unsortedRectList
				tempRect->next = unsortedRectList;
				unsortedRectList = tempRect;
			}

			// Right edge
			if (myRect->x2 > x2) {
				// Create a new rect of just the part that extends beyond the top of myRect
				tempRect = (RectList *)mem_get_from_stash(_G(memtypeRECT), "+guiRectangle");
				if (tempRect == nullptr) {
					error_show(FL, 'OOS!', "vmng_AddRectToRectList");
				}

				tempRect->x1 = x2 + 1;
				tempRect->y1 = myRect->y1;
				tempRect->x2 = myRect->x2;
				tempRect->y2 = myRect->y2;

				// Add tempRect to the unsortedRectList
				tempRect->next = unsortedRectList;
				unsortedRectList = tempRect;
			}

			// Remove myRect from the list and turf it
			if (myRect->next) {
				myRect->next->prev = myRect->prev;
			}
			if (myRect->prev) {
				myRect->prev->next = myRect->next;
			} else {
				rectList = myRect->next;
			}

			mem_free_to_stash((void *)myRect, _G(memtypeRECT));
		}

		// Get the next rect
		myRect = nextRect;
	}

	// Now go through the unsorted list and insert them into the main list
	tempRect = unsortedRectList;
	while (tempRect) {
		unsortedRectList = unsortedRectList->next;
		// For each unsorted rect, loop through the rect list until its place is found
		bool finished = false;
		RectList *prevRect = nullptr;
		myRect = rectList;

		while (myRect && (!finished)) {
			// If it goes before myRect
			if (tempRect->y2 <= myRect->y2) {
				finished = true;
			} else {
				prevRect = myRect;
				myRect = myRect->next;
			}
		}

		// tempRect belongs after prevRect
		if (prevRect) {
			tempRect->prev = prevRect;
			tempRect->next = prevRect->next;
			if (prevRect->next) {
				prevRect->next->prev = tempRect;
			}
			prevRect->next = tempRect;
		} else {
			// else it belongs at the front of rectList
			tempRect->prev = nullptr;
			tempRect->next = rectList;
			if (rectList) {
				rectList->prev = tempRect;
			}
			rectList = tempRect;
		}

		// Get the next unsorted rect
		tempRect = unsortedRectList;
	}

	// Set the screen rect list to the resulting rect list
	*scrnRectList = rectList;
}

} // End of namespace M4
